Android学习笔记17-自定义控件

来源:互联网 发布:帝国cms快速仿站 编辑:程序博客网 时间:2024/05/21 08:49

1、View绘制的基本概念:

我们所有的控件都是继承View这个类的,View的创建主要有3个步骤       measure(测量设置布局的大小) -> layout(设置布局的位置) -> draw(绘制)在这个3个步骤中系统对应有一些回调函数,我们可以在这个回调函数中做我们自己的事。    onMeasure -> onLayout -> onDraw 我们在layout布局中加载我们自己定义的控件需要注意:    a.必须要指定width 和height,因为你不指定就通不过编译,虽然你设置了也没用。    b.自定义的布局一定要加完整的路径,例如:
            <com.example.myswitch.MySwitch                android:id="@+id/ms_switch"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                />
注意:我们在布局预览中也可看到我们定义的布局哦。

2、自定义view步骤:

a. 写类继承View b. 重写onDraw, 进行绘制 c. 重新onMeasure,修改尺寸 d. 在xml布局文件中配置简单的代码演示:(简单的画个矩形)
        public class MySwitch extends View {            private Paint mPaint;//定义个全局的画笔            //系统调用的,这个构造在加载我们xml文件会调用,所以一定要重写这个构造            public MySwitch(Context context, AttributeSet attrs, int defStyle) {                super(context, attrs, defStyle);                initView();            }            //系统调用的,这个构造在加载我们xml文件会调用,所以一定要重写这个构造            public MySwitch(Context context, AttributeSet attrs) {                super(context, attrs);                initView();            }            //这个是我们程序员调用的,如果你不会手动去new这个布局,也可以不用重写这个构造。            public MySwitch(Context context) {                super(context);                initView();            }            //我们不用每次在draw的时候去new 画笔,浪费内存,所以定义个全局的变量            private void initView() {                // 初始化画笔                mPaint = new Paint();                mPaint.setColor(Color.RED);// 画笔颜色            }            // 设置尺寸回调            @Override            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {                // super.onMeasure(widthMeasureSpec, heightMeasureSpec);//查看源码,也是调用setMeasuredDimension                // 将当前控件宽高设置为100x100,就类似画布设置100*100,你draw的矩形比这个大也没用                setMeasuredDimension(100, 100);            }            // measure->layout->draw            // onMeasure->onLayout->onDraw            @Override            protected void onDraw(Canvas canvas) {                // 绘制200x200的矩形                canvas.drawRect(0, 0, 200, 200, mPaint);                System.out.println("onDraw");            }        }
这样我们一个简单的自定义View就实现了。

3、自定义一个开关按钮:

在上面的onMeasure方法中我们直接设置了 100*100 可能会导致布局里面的东西显示不全我们就需要动态的设置宽高了    setMeasuredDimension(mBitmapBg.getWidth(), mBitmapBg.getHeight());// 依据背景图片来确定控件大小代码演示:
public class MySwitch extends View {            private Paint mPaint;            private Bitmap mBitmapBg;            private Bitmap mBitmapSlide;            private int MAX_LEFT;// 滑块最大左边距            private int mSlideLeft;// 当前左边距            private boolean isOpen;// 当前开关状态            public MySwitch(Context context, AttributeSet attrs, int defStyle) {                super(context, attrs, defStyle);                initView();            }            public MySwitch(Context context, AttributeSet attrs) {                super(context, attrs);                initView();                // 加载自定义滑块图片                int slideId = attrs.getAttributeResourceValue(NAMESPACE, "slide", -1);                if (slideId > 0) {                    mBitmapSlide = BitmapFactory.decodeResource(getResources(), slideId);                }            }            public MySwitch(Context context) {                super(context);                initView();            }            private void initView() {                // 初始化画笔                mPaint = new Paint();                mPaint.setColor(Color.RED);// 画笔颜色                // 初始化背景bitmap                mBitmapBg = BitmapFactory.decodeResource(getResources(),R.drawable.switch_background);                // 初始化滑块bitmap                mBitmapSlide = BitmapFactory.decodeResource(getResources(),R.drawable.slide_button);                MAX_LEFT = mBitmapBg.getWidth() - mBitmapSlide.getWidth();                this.setOnClickListener(new OnClickListener() {//在整个view设置点击侦听                    @Override                    public void onClick(View v) {                        if (isClick) {//解决点击和滑动的冲突                            if (isOpen) {                                isOpen = false;// 关闭开关                                mSlideLeft = 0;                            } else {                                isOpen = true;// 打开开关                                mSlideLeft = MAX_LEFT;                            }                            invalidate();// view重绘的方法, 刷新view, 重新调用onDraw方法                            if (mListener != null) {// 回调当前开关状态                                mListener.onCheckChanged(MySwitch.this, isOpen);                            }                        }                    }                });            }            int startX = 0;            int moveX = 0;// 位移距离            boolean isClick;// 标记当前是触摸还是单击事件            @Override            public boolean onTouchEvent(MotionEvent event) {//重写方法,实现滑动切换                switch (event.getAction()) {                case MotionEvent.ACTION_DOWN:                    // 1. 记录起始点x坐标                    startX = (int) event.getX();// 获取相对于当前控件的x坐标                    break;                case MotionEvent.ACTION_MOVE:                    int endX = (int) event.getX();// 2. 记录移动后的x坐标                    int dx = endX - startX;// 3. 记录x偏移量                    mSlideLeft += dx;// 4. 根据偏移量,更新mSlideLeft                    moveX += Math.abs(dx);// 向左向右移动都要统计下来, 所以要用dx绝对值                    if (mSlideLeft < 0) {// 避免滑块超出边界                        mSlideLeft = 0;                    }                    if (mSlideLeft > MAX_LEFT) {// 避免滑块超出边界                        mSlideLeft = MAX_LEFT;                    }                    invalidate();// 5. 刷新界面                    startX = (int) event.getX();// 6. 重新初始化起始点坐标                    break;                case MotionEvent.ACTION_UP:                    if (moveX < 5) {// 根据位移判断是单击事件还是移动事件                        isClick = true;// 单击事件,小于5个像素认为就是点击                    } else {                        isClick = false;// 移动事件,大于5个像素就认为是滑动                    }                    moveX = 0;// 初始化移动的总距离,不然下次会出错                    if (!isClick) {//判断是滑动了                        if (mSlideLeft < MAX_LEFT / 2) {// 根据当前位置, 切换开关状态                            mSlideLeft = 0;// 关闭开关                            isOpen = false;                        } else {                            mSlideLeft = MAX_LEFT;// 打开开关                            isOpen = true;                        }                        invalidate();                        if (mListener != null) {// 回调当前开关状态                            mListener.onCheckChanged(MySwitch.this, isOpen);                        }                    }                    break;                }                return super.onTouchEvent(event);//返回true就消耗了事件,这个是为了解决滑动和点击事件冲突            }            @Override// 设置尺寸回调            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {                setMeasuredDimension(mBitmapBg.getWidth(), mBitmapBg.getHeight());// 依据背景图片来确定控件大小            }            @Override            protected void onDraw(Canvas canvas) {                canvas.drawBitmap(mBitmapBg, 0, 0, null);// 绘制背景图片                canvas.drawBitmap(mBitmapSlide, mSlideLeft, 0, null);// 绘制滑块图片            }            private OnCheckChangeListener mListener;            // 设置开关状态监听            public void setOnCheckChangeListener(OnCheckChangeListener listener) {                mListener = listener;            }            //监听开关状态的回调接口            public interface OnCheckChangeListener {                public void onCheckChanged(View view, boolean isChecked);            }        }
注意:    invalidate()方法是让view失效,如果view是可见的状态,就会立即调用ondraw方法。    在解决点击和滑动事件冲突的时候,我们是通过滑动的距离来判断的。

4、设置自定义View的属性:

例如我们可以直接在xml中设置开关的属性来控制第一次显示的是开还是关,图片的样式。步骤:    a.在res-values的attr.xml文件输入,没有就新建一个:
    <?xml version="1.0" encoding="utf-8"?>            <resources>                <declare-styleable name="MySwitch">                    <attr name="slide" format="reference" />//滑块的属性                    <attr name="checked" format="boolean" />//开关的状态                </declare-styleable>            </resources>
    b.在xml中使用:
            <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"                <!-- 将android替换为我们的包名-->                xmlns:mynamespace="http://schemas.android.com/apk/res/com.example.myswitch"                xmlns:tools="http://schemas.android.com/tools"                android:layout_width="match_parent"                android:layout_height="match_parent" >                <com.mynamespace.myswitch.MySwitch                    android:id="@+id/ms_switch"                    android:layout_width="wrap_content"                    android:layout_height="wrap_content"                    android:layout_centerInParent="true"                    mynamespace:checked="true"                    mynamespace:slide="@drawable/slide_button" />            </RelativeLayout>
    注意:使用我们自定义的属性没有代码提示功能    c.在我们定义的View修改下代码:
            加一个全局变量            private static final String NAMESPACE = "http://schemas.android.com/apk/res/com.example.myswitch";            修改下构造函数,只需要改这个系统调用的就行            public MySwitch(Context context, AttributeSet attrs) {                super(context, attrs);                initView();                // 获取属性值                isOpen = attrs.getAttributeBooleanValue(NAMESPACE, "checked", false);                // 加载自定义滑块图片                int slideId = attrs.getAttributeResourceValue(NAMESPACE, "slide", -1);                if (slideId > 0) {//如果指定了滑块的背景图就加载                    mBitmapSlide = BitmapFactory.decodeResource(getResources(), slideId);                }                if (isOpen) {                    mSlideLeft = MAX_LEFT;                } else {                    mSlideLeft = 0;                }                invalidate();//刷新下界面            }
    这样我们就可以在xml文件中直接设置开关的属性啦。

比较全的开源自定义控件
https://github.com/Trinea/android-open-project/
https://github.com/lightSky/Awesome-MaterialDesign

原创粉丝点击