zoukankan      html  css  js  c++  java
  • Android 自定义的开关按钮——SwitchButton

    本文转自:http://blog.csdn.net/swust_chenpeng/article/details/19967501

    我将原文的控件进行了一些修改,去掉了原来控件的外边框,只留下重要的遮罩、背景和滑块。并且可以在布局文件中预览(预览效果不是太好,凑合看看还可以)。自己修改了下监听器,增加了一些方法。总之目前已经和官方的控件差不多了。重要的是可以自定义控件的大小了!

    上面粉红色的那个就是我们自定义的控件了,下面的两个是用的官方的控件,自己改样式。基本处于没用的级别。

    好了,现在我们开始讲自定义控件添加入代码中。

    首先,定义attrs.xml文件

    layout_width   控件的宽度,必须为确定数值的单位,不能用match_parent/wrap_content
    layout_height  控件的高度,必须为确定数值的单位,不能用match_parent/wrap_content
    mask          控件的遮罩,在这个遮罩范围内的图片才予以显示,可以实现圆角
    track         控件的轨道背景图
    thumb        控件的滑块图片

    这里的5个属性必须同时指定,否则会出问题!

    mask.png

     track.png

    thumb.png(这里可以看见这原点做的时候,应该做到和轨道一个长度,并且正好能在轨道的中间位置)

    效果要达到这样能完美显示 →   ╮(╯▽╰)╭这种事情就交给美工妹子去做吧,程序员就好好地编程吧~

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        
        <declare-styleable name="SwitchButton">
            <attr name="android:layout_width"/>
            <attr name="android:layout_height"/>
            <attr name="mask" format="reference"/>
            <attr name="android:track"/>
            <attr name="android:thumb"/>
        </declare-styleable>
        
    </resources>

    然后,我们写这个控件的类  SwitchButton.java

    package com.kale.switchbutton;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Bitmap;
    import android.graphics.Canvas;
    import android.graphics.Matrix;
    import android.graphics.Paint;
    import android.graphics.PorterDuff.Mode;
    import android.graphics.PorterDuffXfermode;
    import android.graphics.Rect;
    import android.graphics.RectF;
    import android.graphics.drawable.BitmapDrawable;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.View.OnClickListener;
    
    /**
     * @author:swust_chenpeng 
     * 本文转自:http://blog.csdn.net/swust_chenpeng/article/details/19967501
     * @tips : 白色圆点:switch_btn_pressed; 
     *     左白右红的长条:switch_bottom; 
     *     无色长条:switch_frame;
     *     黑色长条:switch_mask.
     * @date :2014-7-20
     */
    public class SwitchButton extends View implements OnClickListener {
        private Bitmap mSwitchTrack, mSwitchThumb, mSwitchMask;
        private float mCurrentX = 0;// 当前的x坐标
        private boolean mSwitchOn = true;// 开关默认是开着的
        private int mMoveLength;// 最大移动距离
        private float mLastX = 0;// 第一次按下的有效区域
    
        private Rect mDest = null;// 绘制的目标区域大小
        private Rect mSrc = null;// 截取源图片的大小
        private int mDeltX = 0;// 移动的偏移量
    
        private int width, height;
    
        private Paint mPaint = null;
        private OnCheckedChangeListener mListener = null;
        private boolean mFlag = false;
        
        private float scaleWidth,scaleHeight;//图片缩放的比例
    
        public SwitchButton(Context context) {
            this(context, null);
        }
    
        public SwitchButton(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
    
        }
    
        public SwitchButton(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init(context, attrs);
        }
    
        /**
         * 初始化相关资源
         */
        private void init(Context context, AttributeSet attrs) {
            TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
                    R.styleable.SwitchButton);
    
            width = (int) mTypedArray.getDimension(
                    R.styleable.SwitchButton_android_layout_width, 100);
            height = (int) mTypedArray.getDimension(
                    R.styleable.SwitchButton_android_layout_height,43);
            
            // 遮罩
            mSwitchMask = getSrcBitmap(mTypedArray, R.styleable.SwitchButton_mask);
            // 背景(轨道的背景)
            mSwitchTrack = getSrcBitmap(mTypedArray, R.styleable.SwitchButton_android_track);
            // 滑块
            mSwitchThumb = getSrcBitmap(mTypedArray, R.styleable.SwitchButton_android_thumb);
            
            // 计算缩放的比例
            scaleWidth = ((float) width / mSwitchMask.getWidth());
            scaleHeight = ((float) height / mSwitchMask.getHeight());
            
            mSwitchMask = getScaleBitmap(mSwitchMask);
            mSwitchTrack = getScaleBitmap(mSwitchTrack);
            mSwitchThumb = getScaleBitmap(mSwitchThumb);    
    
            setOnClickListener(this);
            setOnTouchListener(new OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    return false;
                }
            });
            // 可以移动的长度
            mMoveLength = mSwitchTrack.getWidth() - mSwitchMask.getWidth();
            // 创建一个矩形对象(目标区域的大小),通过使用四个整数来初始化矩形左上角的横坐标、纵坐标以及矩形的高度、宽度
            mDest = new Rect(0, 0, mSwitchMask.getWidth(),mSwitchMask.getHeight());
            // 创建一个矩形对象,矩形左上角的横坐标、纵坐标以及矩形的宽度、高度均为零。这是默认的构造函数
            mSrc = new Rect();
            mPaint = new Paint();
            mPaint.setAntiAlias(true);// 消除锯齿
            mPaint.setAlpha(255);// 设置透明度
            mPaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
        }
        
        // 从资源中获取图片对象
        private Bitmap getSrcBitmap(TypedArray mTypedArray,int index) {
            BitmapDrawable tempBit;
            tempBit = (BitmapDrawable)mTypedArray.getDrawable(index);
            return tempBit.getBitmap();
        }
        
        // 将原图,按照布局文件中的控件长度来缩放
        private Bitmap getScaleBitmap(Bitmap bitmap) {
            Matrix matrix = new Matrix();
            if (isInEditMode()) {
                return bitmap;
            }
            matrix.postScale(scaleWidth, scaleHeight);
                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
                        bitmap.getHeight(), matrix, true);
            return bitmap;
        }
    
        // 该方法指定该控件在屏幕上的大小,这里要计算一下控件的实际大小,然后调用setMeasuredDimension来设置
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(width, height);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            if (mDeltX > 0 || mDeltX == 0 && mSwitchOn) {
                if (mSrc != null) {
                    mSrc.set(mMoveLength - mDeltX, 0,
                            mSwitchTrack.getWidth() - mDeltX, mSwitchMask.getHeight());
                }
            } 
            else if (mDeltX < 0 || mDeltX == 0 && !mSwitchOn) {
                if (mSrc != null) {
                    mSrc.set(-mDeltX, 0, mSwitchMask.getWidth() - mDeltX,
                            mSwitchMask.getHeight());
                }
            }
    
            // 这儿是离屏缓冲,自己感觉类似双缓冲机制吧
            int count = canvas.saveLayer(new RectF(mDest), null,
                    Canvas.MATRIX_SAVE_FLAG 
                    | Canvas.CLIP_SAVE_FLAG
                    | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
                    | Canvas.FULL_COLOR_LAYER_SAVE_FLAG
                    | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
    
            canvas.drawBitmap(mSwitchTrack, mSrc, mDest, null);
            canvas.drawBitmap(mSwitchTrack, mSrc, mDest, null);
            canvas.drawBitmap(mSwitchMask, 0, 0, mPaint);
            canvas.drawBitmap(mSwitchThumb, mSrc, mDest, null);
            //这个判断,用来让这个控件可以在布局文件中预览
            if (isInEditMode()) {
                return ;
            }
            canvas.restoreToCount(count);
            
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                mCurrentX = event.getX();
                mDeltX = (int) (mCurrentX - mLastX);
                // 如果开关开着向左滑动,或者开关关着向右滑动(这时候是不需要处理的)
                if ((mSwitchOn && mDeltX < 0) || (!mSwitchOn && mDeltX > 0)) {
                    mFlag = true;
                    mDeltX = 0;
                }
    
                if (Math.abs(mDeltX) > mMoveLength) {
                    mDeltX = mDeltX > 0 ? mMoveLength : -mMoveLength;
                }
                invalidate();
                return true;
            case MotionEvent.ACTION_UP:
                if (Math.abs(mDeltX) > 0 && Math.abs(mDeltX) < mMoveLength / 2) {
                    mDeltX = 0;
                    invalidate();
                    return true;
                } else if (Math.abs(mDeltX) > mMoveLength / 2
                        && Math.abs(mDeltX) <= mMoveLength) {
                    mDeltX = mDeltX > 0 ? mMoveLength : -mMoveLength;
                    mSwitchOn = !mSwitchOn;
                    if (mListener != null) {
                        mListener.onCheckedChanged(this, mSwitchOn);
                    }
                    invalidate();
                    mDeltX = 0;
                    return true;
                } else if (mDeltX == 0 && mFlag) {
                    // 这时候得到的是不需要进行处理的,因为已经move过了
                    mDeltX = 0;
                    mFlag = false;
                    return true;
                }
                return super.onTouchEvent(event);
            default:
                break;
            }
            invalidate();
            return super.onTouchEvent(event);
        }
    
        public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
            mListener = listener;
        }
    
        public interface OnCheckedChangeListener {
            public void onCheckedChanged(SwitchButton button, boolean isChecked);
        }
    
        @Override
        public void onClick(View v) {
            mDeltX = mSwitchOn ? mMoveLength : -mMoveLength;
            mSwitchOn = !mSwitchOn;
            if (mListener != null) {
                mListener.onCheckedChanged(this, mSwitchOn);
            }
            invalidate();// 重绘
            mDeltX = 0;
        }
        
        public Boolean isChecked() {
            return mSwitchOn;
        }
        
        public void setChecked(Boolean checked) {
            mSwitchOn = checked;
            invalidate();
        }
        
    }

    最后,在布局文件中使用它。(记得写上命名空间)

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:pv="http://schemas.android.com/apk/res/com.kale.switchbutton"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <com.kale.switchbutton.SwitchButton
            android:id="@+id/switch_id"
            android:layout_width="80dp"
            android:layout_height="34dp"
            android:layout_marginTop="20dp"
            android:layout_gravity="center_horizontal"
            android:thumb="@drawable/switch_thumb"
            android:track="@drawable/switch_bottom"
            pv:mask="@drawable/switch_mask" />
        
            <Switch
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="20dp"
                android:layout_gravity="center_horizontal"
                android:switchMinWidth="40dp"
                android:switchPadding="30dp"
                android:padding="5dp"
                android:text="自定义风格"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:textOff=" "
                android:textOn="  "
                android:thumb="@drawable/tt_button"
                android:track="@drawable/tt_path" />
    
        <Switch
            android:id="@+id/switch1"
            android:layout_marginTop="20dp"
            android:layout_gravity="center_horizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    
    
    </LinearLayout>

    源码下载:http://download.csdn.net/detail/shark0017/7659623

  • 相关阅读:
    P2567 [SCOI2010]幸运数字 DFS+容斥定理
    Codeforces Round #462 (Div. 2) C DP
    Codeforces Round #428 (Div. 2) C. dfs
    POJ 2079 最大三角形面积(凸包)
    POJ 3608 凸包间最短距离(旋转卡壳)
    2018年全国多校算法寒假训练
    Educational Codeforces Round 37 E. Connected Components?(图论)
    UVa 1440:Inspection(带下界的最小流)***
    BZOJ 1483:[HNOI2009]梦幻布丁(链表启发式合并)
    PAT L3-016:二叉搜索树的结构(暴力)
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/7412881.html
Copyright © 2011-2022 走看看