• 图文剖析自己定义View的绘制(以自己定义滑动button为例)


    自己定义View一直是横在Android开发人员面前的一道坎。

    一、View和ViewGroup的关系

    从View和ViewGroup的关系来看。ViewGroup继承View。

    View的子类。多是功能型的控件。提供绘制的样式,比方imageView,TextView等。而ViewGroup的子类,多用于管理控件的大小,位置。如LinearLayout,RelativeLayout等。从下图能够看出



    从实际应用中看,他们又是组合关系,我们在布局中,经常是一个ViewGroup嵌套多个ViewGroup或View。而被嵌套的ViewGroup又会嵌套多个ViewGroup或View

    例如以下



    二、View的绘制流程

    从View源代码来看,主要关系三个方法:

    1、measure():測量
         一个final方法,控制控件的大小
    2、layout():布局
             用来控制自己的布局位置
              有相对性,仅仅相对于自己的父类布局,不关心祖宗布局
    3、draw():绘制
              用来控制控件的显示样式

    流程:  流程 measure --> layout --> draw


    相应于我们要实现的方法是

    onMeasure()

    onLayout()

    onDraw()

    实际绘制中。我们的思考顺序通常是这种:

    是否须要控制控件的大小-->是-->onMeasure()
    (1)假设这个自己定义view不是ViewGroup,onMeasure()方法调用setMeasureDeminsion(width,height):用来设置自己的大小
    (2)假设是ViewGroup,onMeasure()方法调用 。child.measure()測量孩子的大小。给出孩子的期望大小值,之后-->setMeasureDeminsion(width,height):用来设置自己的大小

    是否须要控制控件的摆放位置-->是 -->onLayout ()


    是否须要控制控件的样子-->是 -->onDraw ()-->canvas的绘制

    以下是我绘制的流程图:



    以下以自己定义滑动按钮为例,说明自己定义View的绘制流程


    我们期待实现这种效果:


    拖动或点击按钮,开关向右滑动,变成


    当中开关能随着手指的触摸滑动到相应位置。直到最后才固定在开关位置上


    新建一个类继承自View,实现其两个构造方法

    public class SwitchButtonView extends View {
    
        
        public SwitchButtonView(Context context) {
            this(context, null);
        }
    
        public SwitchButtonView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

    drawable资源中加入这两张图片


    借此,我们能够用onMeasure()确定这个控件的大小

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
            mSwitchButton = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
            mSlideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background);
            setMeasuredDimension(mSwitchButton.getWidth(), mSwitchButton.getHeight());
        }

    这个控件并不须要控制其摆放位置,略过onLayout();


    接下来onDraw()确定其形状。

    但我们须要依据我们点击控件的不同行为来确定形状,这须要重写onTouchEvent()

    当中的逻辑是:

    当按下时。触发事件MotionEvent.Action_Down。若此时状态为关:

    (1)若手指触摸点(通过event.getX()得到)在按钮的 中线右側,按钮向右滑动一段距离(event.getX()与开关控件一半宽度之差,详细看下文源代码)

    (2)若手指触摸点在按钮中线左側,按钮依然处于最左(即“关”的状态)。

    若此时状态为开:

    (1)若手指触摸点在按钮中线左側,按钮向左滑动一段距离

    (2)若手指触摸点在按钮中线右側,按钮依然处于最右(即“开”的状态)


    当滑动时。触发时间MotionEvent.Action_MOVE,逻辑与按下时一致

    注意,onTouchEvent()须要设置返回值 为 Return true,否则无法响应滑动事件


    当手指收起时。若开关中线位于整个控件中线左側,设置状态为关。反之,设置为开。

    详细源代码例如以下所看到的:(还对外提供了一个暴露此时开关状态的接口)


    自己定义View部分


    package com.lian.switchtogglebutton;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.graphics.Paint;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.View;
    
    /**
     * Created by lian on 2016/3/20.
     */
    public class SwitchButtonView extends View {
    
        private static final int STATE_NULL = 0;//默认状态
        private static final int STATE_DOWN = 1;
        private static final int STATE_MOVE = 2;
        private static final int STATE_UP = 3;
    
        private Bitmap mSlideButton;
        private Bitmap mSwitchButton;
        private Paint mPaint = new Paint();
        private int buttonState = STATE_NULL;
        private float mDistance;
        private boolean isOpened = false;
        private onSwitchListener mListener;
    
        public SwitchButtonView(Context context) {
            this(context, null);
        }
    
        public SwitchButtonView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
            mSwitchButton = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
            mSlideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button_background);
            setMeasuredDimension(mSwitchButton.getWidth(), mSwitchButton.getHeight());
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            if (mSwitchButton!= null){
                canvas.drawBitmap(mSwitchButton, 0, 0, mPaint);
            }
            //buttonState的值在onTouchEvent()中确定
            switch (buttonState){
                case STATE_DOWN:
                case STATE_MOVE:
                    if (!isOpened){
                        float middle = mSlideButton.getWidth() / 2f;
                        if (mDistance > middle) {
                            float max = mSwitchButton.getWidth() - mSlideButton.getWidth();
                            float left = mDistance - middle;
                            if (left >= max) {
                                left = max;
                            }
                            canvas.drawBitmap(mSlideButton,left,0,mPaint);
                        }
    
                        else {
    
                            canvas.drawBitmap(mSlideButton,0,0,mPaint);
                        }
                    }else{
                        float middle = mSwitchButton.getWidth() - mSlideButton.getWidth() / 2f;
                        if (mDistance < middle){
                            float left = mDistance-mSlideButton.getWidth()/2f;
                            float min = 0;
                            if (left < 0){
                                left = min;
                            }
                            canvas.drawBitmap(mSlideButton,left,0,mPaint);
                        }else{
                            canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint);
                        }
                    }
    
    
    
                    break;
    
                case STATE_NULL:
                case STATE_UP:
                    if (isOpened){
                        Log.d("开关","开着的");
                        canvas.drawBitmap(mSlideButton,mSwitchButton.getWidth()-mSlideButton.getWidth(),0,mPaint);
                    }else{
                        Log.d("开关","关着的");
                        canvas.drawBitmap(mSlideButton,0,0,mPaint);
                    }
                    break;
    
                default:
                    break;
            }
    
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
    
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    mDistance = event.getX();
                    Log.d("DOWN","按下");
                    buttonState = STATE_DOWN;
                    invalidate();
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    buttonState = STATE_MOVE;
                    mDistance = event.getX();
                    Log.d("MOVE","移动");
                    invalidate();
                    break;
    
                case MotionEvent.ACTION_UP:
                    mDistance = event.getX();
                    buttonState = STATE_UP;
                    Log.d("UP","起开");
                    if (mDistance >= mSwitchButton.getWidth() / 2f){
                        isOpened = true;
                    }else {
                        isOpened = false;
                    }
                    if (mListener != null){
                        mListener.onSwitchChanged(isOpened);
                    }
                    invalidate();
                    break;
                default:
                    break;
            }
    
            return true;
        }
    
        public void setOnSwitchListener(onSwitchListener listener){
            this.mListener = listener;
        }
    
        public interface onSwitchListener{
            void onSwitchChanged(boolean isOpened);
        }
    }
    


    DemoActivity:

    package com.lian.switchtogglebutton;
    
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.widget.Toast;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            SwitchButtonView switchButtonView = (SwitchButtonView) findViewById(R.id.switchbutton);
            switchButtonView.setOnSwitchListener(new SwitchButtonView.onSwitchListener() {
                @Override
                public void onSwitchChanged(boolean isOpened) {
                    if (isOpened) {
                        Toast.makeText(MainActivity.this, "打开", Toast.LENGTH_SHORT).show();
                    }else {
                        Toast.makeText(MainActivity.this, "关闭", Toast.LENGTH_SHORT).show();
                    }
                }
            });
        }
    }
    

    布局:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.lian.switchtogglebutton.MainActivity">
    
        <com.lian.switchtogglebutton.SwitchButtonView
            android:id="@+id/switchbutton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />
    </RelativeLayout>
    


  • 相关阅读:
    September 22nd 2016 Week 39th Thursday
    September 21st 2016 Week 39th Wednesday
    android 几个小技巧
    深入理解计算机系统----经验之谈
    输入数字如何获取
    super 和this的用法
    java程序员修炼之道
    eclipse 如何使用svn
    如何手动添加Android Dependencies包
    如何查看正在使用端口号并利用任务管理器将其关闭
  • 原文地址:https://www.cnblogs.com/llguanli/p/8943098.html
Copyright © 2020-2023  润新知