自定义开关滑动按钮控件
package com.loaderman.switch; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; /** * 控件绘制流程: * <p> * 1. measure方法来确定控件尺寸 * 2. layout方法确定控件位置 * 3. draw方法绘制控件内容 * <p> * measure->layout->draw * onMeasure->onLayout->onDraw * <p> * 1280x720 xhdpi * 480x800 hdpi */ public class MySwitch extends View { private Paint paint; private Bitmap mBitmapBg; private Bitmap mBitmapSlide; private int MAX_LEFT;//最大左边距 private int mSlideLeft;//当前左边距 private boolean isOpen = false;//当前开关状态 private static final String NAMESPACE = "http://schemas.android.com/apk/res-auto"; public MySwitch(Context context) { this(context, null); } public MySwitch(Context context, AttributeSet attrs) { this(context, attrs, -1); } public MySwitch(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); //设置开关初始状态 isOpen = attrs.getAttributeBooleanValue(NAMESPACE, "isOpen", false); if(isOpen) { mSlideLeft = MAX_LEFT; }else { mSlideLeft = 0; } //设置滑块图片 int slideDrawable = attrs.getAttributeResourceValue(NAMESPACE, "slideDrawable", -1); mBitmapSlide = BitmapFactory.decodeResource(getResources(), slideDrawable); invalidate(); } private void init() { paint = new Paint(); paint.setColor(Color.RED); //初始化背景图片 mBitmapBg = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); //滑块图片 mBitmapSlide = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button); MAX_LEFT = mBitmapBg.getWidth() - mBitmapSlide.getWidth(); //设置点击事件 this.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (isClick) { if (isOpen) { mSlideLeft = 0; isOpen = false; } else { mSlideLeft = MAX_LEFT; isOpen = true; } //刷新当前控件 invalidate();//此方法会导致onDraw重新走一遍 //调用回调对象的方法, 来回传数据 if (listener != null) { listener.onCheckedChanged(isOpen); } } } }); //this.setOnTouchListener(); } private int startX; private int moveX;//记录移动总距离 private boolean isClick;//标记当前是否是点击事件 @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //1.记录起点坐标 startX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE: //2. 记录移动后坐标 int endX = (int) event.getX(); //3. 计算偏移量 int dx = endX - startX; moveX += Math.abs(dx); //4. 根据偏移量更新控件位置 mSlideLeft += dx; //避免越界 if (mSlideLeft < 0) { mSlideLeft = 0; } if (mSlideLeft > MAX_LEFT) { mSlideLeft = MAX_LEFT; } invalidate(); //5. 重新初始化起点坐标 startX = endX; break; case MotionEvent.ACTION_UP: //根据移动总距离确定是否是点击事件 if (moveX > 5) {//允许5个像素的误差 //移动 isClick = false; } else { //点击 isClick = true; } moveX = 0;//移动总距离一定要归0; if (!isClick) { //确定最终开关状态 if (mSlideLeft < MAX_LEFT / 2) { mSlideLeft = 0; isOpen = false; } else { mSlideLeft = MAX_LEFT; isOpen = true; } invalidate(); //调用回调对象的方法, 来回传数据 if (listener != null) { listener.onCheckedChanged(isOpen); } } break; default: break; } //return true;//消费了此事件,导致onClick无法响应 ACTION_DOWN + ACTION_MOVE..... + ACTION_UP //return false;//不消费事件,只会走ACTION_DOWN, 后续所有事件不都不处理了 return super.onTouchEvent(event);//由于onClick的回调是在super.onTouchEvent完成的, // 所以必须返回父类的onTouchEvent才能保证onClick也能够响应事件 } //重写此方法可以修改控件尺寸 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec); //setMeasuredDimension(100, 100); setMeasuredDimension(mBitmapBg.getWidth(), mBitmapBg.getHeight()); System.out.println("onMeasure...."); } //设置控件位置 @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); System.out.println("onLayout...."); } //绘制控件 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //canvas.drawRect(0, 0, 200, 200, paint); System.out.println("onDraw......"); //在左上角绘制图片 canvas.drawBitmap(mBitmapBg, 0, 0, null); canvas.drawBitmap(mBitmapSlide, mSlideLeft, 0, null); } private OnCheckedChangeListener listener; public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { this.listener = listener; } //定义回调接口 public interface OnCheckedChangeListener { void onCheckedChanged(boolean isChecked); } // public void smsBackup(OnCheckedChangeListener callback) { // // callback.onCheckedChanged(true); // } }
package com.loaderman.switch; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private MySwitch mSwitch; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSwitch = (MySwitch) findViewById(R.id.my_switch); mSwitch.setOnCheckedChangeListener(new MySwitch.OnCheckedChangeListener() { @Override public void onCheckedChanged(boolean isChecked) { Toast.makeText(MainActivity.this, "开关状态:" + isChecked, Toast.LENGTH_SHORT).show(); } }); } }
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.loaderman.switch.MainActivity"> <com.loaderman.switch.MySwitch android:id="@+id/my_switch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" app:isOpen="true" app:slideDrawable="@drawable/slide_button" /> </RelativeLayout>
自定义属性atts_my_switch.xml
<resources> <declare-styleable name="MySwitch"> <!--开关状态--> <attr name="isOpen" format="boolean"/> <!--滑块图片--> <attr name="slideDrawable" format="reference"/> </declare-styleable> </resources>
效果图:
最后,关注【码上加油站】微信公众号后,有疑惑有问题想加油的小伙伴可以码上加入社群,让我们一起码上加油吧!!!