Android重写View实现全新的控件

yizhihongxing

那么让我们来详细讲解一下“Android重写View实现全新的控件”的完整攻略。

什么是重写View

在Android中,View是用户界面的基本构建单元,绝大部分控件都是基于View的,因此我们可以通过重写View来实现我们自定义的控件。

在进行View的重写时,通常需要继承View或者它的子类,然后重写对应的方法。View的子类较多,它们之间的主要区别在于有一些默认的绘制行为和触摸反馈。

实现步骤

  1. 继承View或者它的子类,并重写对应方法。

扩展自View,通常需要重写onDraw方法。这是一个绘制你自定义View的方法,你需要在该方法中实现自己想要的绘制,例如绘制图像、绘制文字等。

public class MyView extends View {
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 在这里进行绘制操作
    }
}

扩展自ViewGroup,通常需要重写onLayout方法和onMeasure方法。onMeasure方法用于决定目标View的大小,onLayout用于确定它的位置。

public class MyViewGroup extends ViewGroup {
    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 在这里进行子View的布局操作
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 在这里计算目标View的大小
    }
}
  1. 实现一些自定义属性。

在大多数情况下,自定义ViewGroup和自定义View通常需要一些自定义属性。这些属性允许你自定义View的各种方面,例如:颜色、大小、样式等。

可以通过在xml中定义属性,然后在自定义View中进行解析,来实现添加自定义属性的功能。下面是一个简单的例子。

在res/values/attrs.xml中添加以下内容:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyView">
        <attr name="my_attribute" format="string"/>
    </declare-styleable>
</resources>

在xml中使用MyView时,可以定义一个my_attribute,例如:

<com.example.MyView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:my_attribute="value"/>

在自定义View类中引入属性并解析:

public class MyView extends View {
    private String mAttributeValue;

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.getTheme().obtainStyledAttributes(
            attrs,
            R.styleable.MyView,
            0, 0);

        try {
            mAttributeValue = a.getString(R.styleable.MyView_my_attribute);
        } finally {
            a.recycle();
        }
    }
}
  1. 处理触摸输入事件。

如果你想让你的自定义View能够响应触摸事件,那么就需要处理它们。你可以通过覆盖onTouchEvent方法来处理输入事件。

public class MyView extends View {
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 处理点击事件
                break;
            case MotionEvent.ACTION_MOVE:
                // 处理移动事件
                break;
            case MotionEvent.ACTION_UP:
                // 处理放开事件
                break;
        }
        return true;
    }
}

示例

现在我们来看两个实际的例子,来更好的理解如何重写View创建自定义控件。

自定义圆形进度条

我们来尝试实现一个自定义的圆形进度条控件。实现的主要步骤如下:

  1. 自定义View来承载该控件。
public class CircleProgressBar extends View {
    // ...
}
  1. 实现属性。

定义自定义控件的一些属性,例如底部圆形颜色、进度圆环颜色、圆环宽度、最大进度值等。

<declare-styleable name="CircleProgressBar">
    <attr name="bottomColor" format="color"/>
    <attr name="progressColor" format="color"/>
    <attr name="strokeWidth" format="dimension"/>
    <attr name="maxValue" format="integer"/>
</declare-styleable>
  1. 重写onDraw方法,代码中包含如何画一个圆环及进度条的描述。
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //绘制背景
    canvas.drawArc(oval, startAngle, sweepAngle, false, mPaintBottom);
    //绘制进度条进度值
    canvas.drawArc(oval, startAngle, progressValue / maxValue * sweepAngle, false, mPaintProgress);
}
  1. 添加setXXX方法,并在其中进行相应的处理。
public void setProgressValue(int progress) {
    if (progress < 0) {
        throw new IllegalArgumentException("progress < 0");
    }
    this.progressValue = progress;
    invalidate();
}

自定义流式布局

流式布局是Android中较为受欢迎的布局方式之一,它可以自适应子View的宽高,从而在屏幕上按照一行或多行排列。

实现的主要步骤如下:

  1. 继承ViewGroup类。
public class FlowLayout extends ViewGroup {
    public FlowLayout(Context context) {
        super(context);
        inflater = LayoutInflater.from(context);
    }

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        inflater = LayoutInflater.from(context);
    }

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        inflater = LayoutInflater.from(context);
    }
    // ...
}
  1. 重写onMeasure方法,进行布局的测量和排列。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);

    int lineWidth = 0;
    int lineHeight = 0;

    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        View childView = getChildAt(i);
        measureChild(childView, widthMeasureSpec, heightMeasureSpec);

        MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
        int childWidth = childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
        int childHeight = childView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;

        if (lineWidth + childWidth > width) {
            width = Math.max(width, lineWidth);
            lineWidth = childWidth;
            height += lineHeight;
            lineHeight = childHeight;
        } else {
            lineWidth += childWidth;
            lineHeight = Math.max(lineHeight, childHeight);
        }

        if (i == childCount - 1) {
            width = Math.max(width, lineWidth);
            height += lineHeight;
        }
    }
    setMeasuredDimension(width, height);
}
  1. 重写onLayout方法,对子View进行排列。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    mAllViews.clear();
    mLineHeights.clear();

    int width = getWidth();

    int lineWidth = 0;
    int lineHeight = 0;

    List<View> lineViews = new ArrayList<>();
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        View childView = getChildAt(i);

        MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
        int childWidth = childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
        int childHeight = childView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;

        if (lineWidth + childWidth > width) {
            mLineHeights.add(lineHeight);
            mAllViews.add(lineViews);

            lineWidth = 0;
            lineHeight = childHeight;

            lineViews = new ArrayList<>();
        }

        lineWidth += childWidth;
        lineHeight = Math.max(lineHeight, childHeight);
        lineViews.add(childView);
    }

    mLineHeights.add(lineHeight);
    mAllViews.add(lineViews);

    int left = 0;
    int top = 0;
    int lineCount = mAllViews.size();
    for (int i = 0; i < lineCount; i++) {
        lineViews = mAllViews.get(i);
        lineHeight = mLineHeights.get(i);

        for (int j = 0; j < lineViews.size(); j++) {
            View childView = lineViews.get(j);
            if (childView.getVisibility() == View.GONE) {
                continue;
            }

            MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();

            int lc = left + layoutParams.leftMargin;
            int tc = top + layoutParams.topMargin;
            int rc = lc + childWidth;
            int bc = tc + childHeight;

            childView.layout(lc, tc, rc, bc);
            left += layoutParams.leftMargin + childWidth + layoutParams.rightMargin;
        }
        left = 0;
        top += lineHeight;
    }
}
  1. 添加removeAllViews和addView方法。
public void removeAllViews(boolean isRemoveHead) {
    if (isRemoveHead) {
        try {
            for (int i = getChildCount() - 1; i >= 0; i--) {
                removeViewAt(i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    } else {
        removeAllViews();
    }
}

public void addChildView(View child, boolean isNewline) {
    addView(child, childLayoutParams(isNewline));
}

结语

以上是重写View实现全新的控件的攻略,可以通过这些步骤和例子来实现更多的自定义控件,如果你想深入学习Android自定义控件,可以学习更多的基本知识,例如如何绘制Path和Canvas、如何处理手势操作等等。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Android重写View实现全新的控件 - Python技术站

(0)
上一篇 2023年6月11日
下一篇 2023年6月11日

相关文章

  • javascript自定义函数参数传递为字符串格式

    当定义一个自定义函数时,我们可以定义该函数拥有的参数列表。这些参数可以是任何类型的,如字符串、数字、布尔值、数组和对象等。当我们调用这个函数时,我们必须传递与函数定义中声明的参数类型相匹配的参数。下面是关于如何将字符串格式作为函数参数传递的完整攻略。 1. 将字符串作为函数的参数 我们可以将字符串值作为自定义函数的参数,这个字符串可以是任何东西,例如一个句子…

    JavaScript 2023年5月28日
    00
  • html5笛卡尔心形曲线的实现

    实现一个笛卡尔心形曲线,可以使用HTML5 canvas绘制,代码实现如下: HTML代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>HTML5笛卡尔心形曲线的实现</title> <…

    JavaScript 2023年5月28日
    00
  • javascript表单验证以及正则表达式举例详解

    JavaScript表单验证以及正则表达式详解 在前端开发中,表单验证是必不可少的环节之一。JavaScript提供了强大的正则表达式功能,可以用来验证输入内容的格式是否符合所需规则。本文将详细讲解JavaScript表单验证以及正则表达式的使用方法。 表单验证 在表单提交数据前,我们需要对用户输入的数据进行验证,确保数据的格式符合要求。例如,一个注册表单需…

    JavaScript 2023年6月10日
    00
  • JavaScript获取字符串实际长度(包含中英文)

    获取字符串实际长度是一个比较常见的问题,由于中英文字符在内存中占用的字节数不同,所以它们在字符串长度计算上也不同。在JavaScript中,我们可以使用以下方法获取一个字符串的实际长度。 方法一:通过正则匹配 正则表达式可以用来匹配所有非英文字符,我们可以使用它来判断字符串中是否包含中文字符。代码如下: function getLength(str) { r…

    JavaScript 2023年5月19日
    00
  • JavaScript设置获取和设置属性的方法

    JavaScript中的对象都有属性,这些属性通常指的是对象的特性。获取和设置属性是JavaScript中最基础的操作之一,它是我们在实际开发中经常会用到的操作。 获取属性的值 JavaScript中有很多方式可以获取属性的值,例如使用.操作符或[]操作符来获取属性的值。使用.操作符获取属性的值,语法如下: 对象.属性名 例如: var person = {…

    JavaScript 2023年6月11日
    00
  • 在JavaScript中操作时间之getUTCDate()方法的使用

    当我们在JavaScript中需要操作时间时,getUTCDate()是一个非常实用的方法,它可以获取当前时间基于协调世界时(UTC)的日期中的天数,即1到31之间的整数值。 方法语法 getUTCDate()方法的语法如下: dateObject.getUTCDate() 其中,dateObject表示需要获取日期的Date对象。 方法返回值 getUTC…

    JavaScript 2023年5月27日
    00
  • javascript 跨浏览器的事件系统

    JavaScript 跨浏览器的事件系统是指在各种浏览器下实现统一的事件,保证我们开发的代码能够在各种浏览器下都能正确的运行,不受浏览器差异的影响。以下是实现 JavaScript 跨浏览器的事件系统的完整攻略。 创建跨浏览器的事件处理程序 我们可借助 W3C 的标准事件模型来创建跨浏览器的事件处理程序,代码如下: //创建事件处理程序 function a…

    JavaScript 2023年6月10日
    00
  • javascript 补零 函数集合

    标题: JavaScript 补零 函数集合 介绍:在 JavaScript 中,有时候我们需要对数字进行处理,让它们保持一定的长度,并在前面添加 “0” (零) ,这时候就需要用到补零函数。本文将详细讲解 JavaScript 补零 函数集合和应用场景。 函数列表 函数一:补零函数补充 函数二:转化成固定长度字符串函数 函数三:Date 对象转化成指定格式…

    JavaScript 2023年5月27日
    00
合作推广
合作推广
分享本页
返回顶部