4.1 自定义控件的原理、流程与实现

4.1 自定义控件的原理、流程与实现_第1张图片

点此进入:从零快速构建APP系列目录导图
点此进入:UI编程系列目录导图
点此进入:四大组件系列目录导图
点此进入:数据网络和线程系列目录导图

本节例程下载地址:
WillFLowCustomView
WillFlow_FollowBallView

首先看一下本篇实现的效果图:

圆形百分比例显示控件:

4.1 自定义控件的原理、流程与实现_第2张图片

跟随手指移动小球控件

一、什么是自定义控件?

对于Android应用开发者而言,Android 中自带的控件并不陌生,常用的单独控件有TextView(文本框)、EditText(编辑框)、Button( 按钮)、ImageView(图片视图)等,组合控件(即用来摆放多个单独控件的容器)常见的有LinearLayout(线性布局)、RelativeLayout(相对布局)、TableLayout(表格布局)、FrameLayout(帧布局)等。那么什么是自定义控件呢?自定义控件是基于项目开发中定制化视图的需要,或为了提高复用性而基于既有控件扩展封装的控件。

二、为什么要自定义控件?

在大多数情况下,Android 为开发者提供的这些控件已经足够我们使用,并且对于没有怎么使用自定义控件的程序员来说,对自定义控件多少会有点抵触,因为他们会觉得Android系统已为程序员提供了大量控件足够开发使用,而且网上开源的控件也足够丰富,何需自己去自定义。但在某些情况下项目中需要定制化视图,这个时候就不得不使用到自定义控件方面的知识了。

所以了解自定义的相关知识,以及相关原理是非常必要的,这样我们就可以基于系统自带的控件扩展封装符合项目所需的控件。有人说网上开源的自定义控件不是可以拿过来直接用的吗?但有时候这些自定义控件是没法完全满足项目开发需要的,这个时候若是了解了自定义知识,就可以更加方便的修改这些开源自定义控件为己所用,这就是为什么我们需要自定义控件

三、自定义控件三大类型

  • 组合已有的控件实现
  • 完全自定义控件(继承View, ViewGroup)
  • 继承已有的控件实现(扩展已有的功能)

四、自定义View的步骤

1、自定义View(ViewGroup)的属性
2、在 View(ViewGroup)的构造方法中获得我们自定义的属性
3、重写 onMeasure(简单的应用中有时不需要重写)
4、重写 onDraw(基于Android控制扩展的有时不需要重写)

五、自定义控件基本绘制原理

View的绘制基本上由measure()、layout()、draw()这个三个函数完成。

(1)Measure过程是计算视图大小,其过程相关方法主要有三个:

public final void measure(int widthMeasureSpec, int heightMeasureSpec)
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

measure()调用onMeasure(),onMeasure()测量宽度、高度然后调用setMeasureDimension()保存测量结果。其中measure、setMeasureDimension是final类型,在view的子类不需要重写,而onMeasure()在view的子类中需要重写。

关于MeasureSpec:

一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。它有三种模式:
- UPSPECIFIED(未指定):父容器对于子容器没有任何限制,子容器想要多大就多大。
- EXACTLY(完全):父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间。
- AT_MOST(至多):子容器可以是声明其大小内的任意大小。

(2)Layout用于设置视图在屏幕中显示的位置,其相关方法主要有三个:

public void layout(int l, int t, int r, int b)
protected boolean setFrame(int left, int top, int right, int bottom)
protected void onLayout(boolean changed, int left, int top, int right, int bottom)

onLayout一般在自定义ViewGroup中使用的更多一些。

(3)draw过程主要用于利用前两步得到的参数,将视图显示在屏幕上:

public void draw(Canvas canvas)
protected void onDraw(Canvas canvas)

通过调用draw函数进行视图绘制,在View类中onDraw函数是个空函数,最终的绘制需求需要在自定义的onDraw函数中进行实现,比如ImageView完成图片的绘制,如果是自定义ViewGroup的话,这个函数则不需要重载。

六、自定义控件示例

(1)圆形显示百分比例的控件

  • PercentView.java
public class PercentView extends View {
    private final static String TAG = PercentView.class.getSimpleName();
    private Paint mPaint;
    private RectF mRectF;

    public PercentView(Context context) {
        super(context);
        init();
    }

    public PercentView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PercentView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mRectF = new RectF();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
                break;
            case MeasureSpec.UNSPECIFIED:
                break;
        }
        Log.i(TAG, "onMeasure() widthMode  : " + widthMode);
        Log.i(TAG, "onMeasure() widthSize  : " + widthSize);
        Log.i(TAG, "onMeasure() heightMode : " + heightMode);
        Log.i(TAG, "onMeasure() heightSize : " + heightSize);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.i(TAG, "onLayout()");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.YELLOW);
        // FILL:填充; STROKE:描边; FILL_AND_STROKE:填充和描边。
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        int width = getWidth();
        int height = getHeight();
        Log.i(TAG, "onDraw() width : " + width + ", height : " + height);
        float radius = width / 4;
        canvas.drawCircle(width / 2, width / 2, radius, mPaint);
        mPaint.setColor(getResources().getColor(R.color.colorPrimary));
        // 用于定义的圆弧的形状和大小的界限
        mRectF.set(width / 2 - radius, width / 2 - radius, width / 2 + radius, width / 2 + radius);
        // 根据进度画圆弧
        canvas.drawArc(mRectF, -150, 120, true, mPaint);
    }
}
  • 然后在我们的布局文件中使用我们的自定义控件:
<com.wgh.willflowcustomview.PercentView
        android:id="@+id/percentView"
        android:layout_width="match_parent"
        android:layout_height="235dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentTop="true" />

(2)跟随手指移动的小球的控件

  • MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 获取布局文件中的LinearLayout容器
        LinearLayout linearLayout = (LinearLayout) findViewById(R.id.notification_main_column);
        // 创建DrawView组件
        final FollowBallView followBallView = new FollowBallView(this);
        // 设置自定义组件的最小宽度、高度
        followBallView.setMinimumWidth(300);
        followBallView.setMinimumHeight(500);
        linearLayout.addView(followBallView);
    }
}
  • FollowBallView.java
public class FollowBallView extends View{
    private float mCurrentX = 66;
    private float mCurrentY = 66;
    // 定义、并创建画笔
    private Paint mPaint = new Paint();

    public FollowBallView(Context context) {
        super(context);
    }

    public FollowBallView(Context context, AttributeSet set) {
        super(context, set);
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 设置画笔的颜色
        mPaint.setColor(Color.BLUE);
        // 绘制一个小圆(作为小球)
        canvas.drawCircle(mCurrentX, mCurrentY, 66, mPaint);
    }

    // 为该组件的触碰事件重写事件处理方法
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 修改currentX、currentY两个属性
        mCurrentX = event.getX();
        mCurrentY = event.getY();
        // 通知当前组件重绘自己
        invalidate();
        // 返回true表明该处理方法已经处理该事件
        return true;
    }
}

注释里面写的还是比较全面,相信大家都能看懂。到此为止我们就明学会了Android自定义控件的基本绘制原理,我们会在下一篇中介绍如何自定义属性。

点此进入:GitHub开源项目“爱阅”。“爱阅”专注于收集优质的文章、站点、教程,与大家共分享。下面是“爱阅”的效果图:


联系方式:

简书:WillFlow
CSDN:WillFlow
微信公众号:WillFlow

微信公众号:WillFlow

你可能感兴趣的:(大叨安卓-UI编程)