android自定义View----ToggleBotton

一、自定义View的步骤

1、自定义属性,将外部设置的参数定义为自定义属性,方便使用

2、重写onMeasure方法,View大小为wrap_content时根据View的特点计算宽高(注意如果支持padding测量时也要处理padding),match_parent和具体值时使用传入的widthMeasureSpec和heightMeasure的尺寸。通过setMeasureDimension设置具体的测量宽高。

3、重写onDraw方法,绘制界面(图片、文字、圆形、方形、横线等),如果要支持padding需要处理padding。

4、有滑动的重写onTouchEvent方法,处理滑动。处理完后调用invalidate方法触发onDraw重新绘制界面,这样达到滑动的效果。

5、定义Listener接口,外部通过接口知道View的状态。

注意:margin不用在View中考虑,是父View处理margin。padding需要在View中考虑即绘制时位置需要减去padding值,否则padding没有效果。

二、自定义ToggleBotton

下面以自定义ToggleBotton为例子介绍自定义View的详细流程。系统ToggleButton通过设置背景图片也可以达到常用的效果,但是没有滑动的效果。效果如下:

1、自定义属性

1)、在values目录下新建attrs.xml文件

 2)、在attrs.xml中添加declare-styleable,一个declare-styleable就表示一个View的自定义属性,代码如下:

    <declare-styleable name="ToggleButton">
        <attr name="open_color" format="color"/>
        <attr name="close_color" format="color"/>
        <attr name="init_status" format="boolean"/>
    </declare-styleable>

其中name是自定义View的类名,<attr name 就是属性名

3)、使用自定义属性。先定义命名空间

xmlns:app="http://schemas.android.com/apk/res-auto"

使用如下:

app:open_color="@android:color/holo_red_light"

4)、在View中如何获取布局文件中定义的自定义属性?

        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ToggleButton);
        mOpenColor = a.getColor(R.styleable.ToggleButton_open_color, Color.GREEN);
        mCloseColor = a.getColor(R.styleable.ToggleButton_close_color, Color.WHITE);
        mIsOpen = a.getBoolean(R.styleable.ToggleButton_init_status, true);
        a.recycle();

attrs是构造函数中传入的。注意属性获取完了后要调用a.recycle()

2、重写onMeasure

在宽高为wrap_content时自己根据View的特点设置测量宽高,match_parent和具体值时使用父View传入的measureSpec。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

        if (widthSpecMode != MeasureSpec.EXACTLY) {
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(mDefaultWidth + getPaddingLeft() + getPaddingRight(), MeasureSpec.EXACTLY);
        }
        if (heightSpecMode != MeasureSpec.EXACTLY) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(mDefaultHeight + getPaddingTop() + getPaddingBottom(), MeasureSpec.EXACTLY);
        }

        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
    }

3、重写onDraw

主要绘制三部分内容:

1)、绘制背景,默认是关闭时是白色,打开时是绿色

2)、绘制圆形按钮,和关闭时颜色一致

3)、关闭时绘制边框,因为关闭时是白色,可能和其父容器颜色一样,用边框加以区分

代码如下:

    protected void onDraw(Canvas canvas) {
        // 绘制背景
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mIsOpen ? mOpenColor : mCloseColor);
        RectF rect = new RectF(left, top, right, bottom);
        canvas.drawRoundRect(rect, mRroundConerRadius, mRroundConerRadius, mPaint);

        // 绘制圆形按钮
        mPaint.setColor(mCloseColor);
        if (mIsSliding == false) {
            mCircleButtonX = mIsOpen ? measureWidth - mRroundConerRadius : mRroundConerRadius;
            mCircleButtonX += getPaddingLeft();
        }
        canvas.drawCircle(mCircleButtonX, mCircleButtonY, mCircleButtonRadius, mPaint);

        // 关闭时绘制边框
        if (mIsOpen == false) {
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(dp2px(1));
            mPaint.setColor(0XffDDDDDD);
            canvas.drawRoundRect(rect, mRroundConerRadius, mRroundConerRadius, mPaint);
            canvas.drawCircle(mCircleButtonX, mCircleButtonY, mCircleButtonRadius, mPaint);
        }
    }

重写onDraw实际就是用canvas绘制图形,绘制自定义View需要的图形。上面绘制了圆角矩形、圆形以及边框。注意绘制边框时要设置paint的style为STROKE,默认是FILL填充内部。

4、滑动处理

为了实现圆形按钮的滑动效果,我们需要重写onTouchEvent方法,获取每次滑动的距离,根据这个距离重写绘制View即重新回调onDraw(调用invalidate就会回调onDraw),从而达到滑动的效果。代码如下:

    private float actionDownX;
    private long actionDownTime;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                actionDownX = event.getX();
                actionDownTime = System.currentTimeMillis();
                Log.d(TAG, "downx:" + actionDownX + ",downy:" + event.getY());
                break;
            case MotionEvent.ACTION_MOVE:
                mIsSliding = true;

                float moveX = event.getX() - actionDownX;
                actionDownX = event.getX();
                mCircleButtonX += moveX;

                // 圆形按钮的边界限制
                if (mCircleButtonX > measureWidth - mRroundConerRadius) {
                    mCircleButtonX = measureWidth - mRroundConerRadius;
                } else if (mCircleButtonX < mRroundConerRadius) {
                    mCircleButtonX = mRroundConerRadius;
                }

                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                boolean oldIsOpen = mIsOpen;

                if (System.currentTimeMillis() - actionDownTime < 300) {
                    // 单击按钮的情况
                    mIsOpen = !mIsOpen;
                } else {
                    // 滑动时位置超过一半打开,否则关闭
                    if (mCircleButtonX > measureWidth * 0.5f) {
                        mIsOpen = true;
                    } else {
                        mIsOpen = false;
                    }
                }
                invalidate();

                mIsSliding = false;
                break;
        }
        return true;
    }

一个完整的点击事件回调如下:ACTION_DOWN->ACTION_UP或者ACTION_DOWN->ACTION_MOVE(一个或者多个)->ACTION_UP。

因为只能在x轴上滑动,所以我们只获取了在x轴上滑动的距离。这里有两种情况,一种是点击,一种是滑动。可以通过该次点击事件的时间区分是滑动还是点击,时间小于300ms时认为是点击,大于时认为是滑动。

还要注意滑动边界的处理。

5、设置Listener

对外提供接口监听ToggleButton开关状态的切换

Listener接口类:

    public interface ToggleButtonStatusListener {
        /**
         * 状态改变时回调
         */
        void onChange(boolean isOpen);
    }

View中开关状态改变时回调监听器:

            case MotionEvent.ACTION_UP:
                boolean oldIsOpen = mIsOpen;

                if (System.currentTimeMillis() - actionDownTime < 300) {
                    // 单击按钮的情况
                    mIsOpen = !mIsOpen;
                } else {
                    // 滑动时位置超过一半打开,否则关闭
                    if (mCircleButtonX > measureWidth * 0.5f) {
                        mIsOpen = true;
                    } else {
                        mIsOpen = false;
                    }
                }
                invalidate();

                // 状态改变时回调
                if (oldIsOpen != mIsOpen && mToggleButtonStatusListener != null) {
                    mToggleButtonStatusListener.onChange(mIsOpen);
                }

                mIsSliding = false;
                break;

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值