自定义控件-自定义开关
布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:yzx="http://schemas.android.com/apk/res/com.yzx.mytogglebutton" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <com.yzx.mytogglebutton.MyToggleButton android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" heima:slide_button="@drawable/slide_button" heima:switch_background="@drawable/switch_background" /> </RelativeLayout>
com.yzx.togglebutton.ToggleButton类的实现和自定义属性的使用。
业务逻辑实现
1.添加自定义属性。在values目录下创建attrs.xml文件
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MyToggleBtn"> <attr name="slide_button" format="reference"/> <attr name="switch_background" format="reference"/> </declare-styleable> </resources>
2.在布局文件中用到自定义属性时需要引入自定义的命名空间。
在activity_main.xml布局中引入如下命名空间:
xmlns:yzx="http://schemas.android.com/apk/res/com.example.togglebutton"
com.example.togglebutton是包名,其实名字可以自定义,并不是非得用类名,但是我们习惯用类型作为自定义属性的命名空间。
3.自定义类MyToggleButton继承View类。
MyToggleButton.java
package com.itheima.mytogglebutton; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.BitmapDrawable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; public class MyToggleButton extends View implements OnClickListener { // 该按钮其实是在一个背景图片上叠加一个图片 private Bitmap slide_button; private Bitmap switch_background; private Paint paint; int slideLeft = 0; int slideLeftMax; // 当前是开或者关,开关的状态 boolean toggleState = false; int firstX; int lastX; // 记录用户当前是点击还是滑动 boolean isDrop; // 自定义控件必须覆写父类的三个构造函数 public MyToggleButton(Context context, AttributeSet attrs) { super(context, attrs); // int count = attrs.getAttributeCount(); // for (int i = 0; i < count; i++) { // System.out.println(attrs.getAttributeName(i) + ":" + // attrs.getAttributeValue(i)); // } // arg0:要解析的属性在哪个AttributeSet中 // arg1:把要解析的两个自定义属性的id封装在这个int数组中 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyToggleBtn); BitmapDrawable bd1 = (BitmapDrawable) array.getDrawable(R.styleable.MyToggleBtn_slide_button); BitmapDrawable bd2 = (BitmapDrawable) array.getDrawable(R.styleable.MyToggleBtn_switch_background); // 加载两张背景图 slide_button = bd1.getBitmap(); switch_background = bd2.getBitmap(); // slide_button = BitmapFactory.decodeResource(getResources(), // R.drawable.slide_button); // switch_background = BitmapFactory.decodeResource(getResources(), // R.drawable.switch_background); slideLeftMax = switch_background.getWidth() - slide_button.getWidth(); paint = new Paint(); // 给自己设置点击侦听 setOnClickListener(this); } // 测量 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 指定组件宽高 setMeasuredDimension(switch_background.getWidth(), switch_background.getHeight()); } // 绘制 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 绘制两张图片 canvas.drawBitmap(switch_background, 0, 0, paint); canvas.drawBitmap(slide_button, slideLeft, 0, paint); } @Override public void onClick(View v) { // 先判断用户到底是想点击还是想滑动 if (!isDrop) { if (toggleState) { slideLeft = 0; } else { slideLeft = slideLeftMax; } toggleState = !toggleState; } invalidate(); } // 触摸事件 @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: firstX = lastX = (int) event.getX(); isDrop = false; break; case MotionEvent.ACTION_MOVE: int newX = (int) event.getX(); // 计算出本次move事件和上一次move事件之间位移的像素 int offsetX = newX - lastX; slideLeft += offsetX; lastX = newX; // 判断当前用户是点击按钮还是在滑动按钮 // 如果滑动距离小于6,就算是点击 if (Math.abs(newX - firstX) <= 6) { isDrop = false; } else { // 如果滑动距离大于6,就算是用户在滑动 isDrop = true; } break; case MotionEvent.ACTION_UP: if (isDrop) { // 检测手指抬起时,滑块的位移像素是否大于slideLeftMax的一半 int upX = (int) event.getX(); // 往右滑动,是否大于一半 if (upX - firstX > slideLeftMax / 2) { toggleState = true; } // 往左滑动,是否大于一半 else if (firstX - upX > slideLeftMax / 2) { toggleState = false; } flushViewState(); } break; } flushViewBound(); return true; } private void flushViewBound() { if (slideLeft < 0) { slideLeft = 0; } else if (slideLeft > slideLeftMax) { slideLeft = slideLeftMax; } invalidate(); } private void flushViewState() { if (toggleState) { slideLeft = slideLeftMax; } else { slideLeft = 0; } } }
MyView.java
package com.itheima.mytogglebutton; import android.R.color; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; public class MyView extends View { public MyView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } // 测量宽高(确定宽高) @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), // widthMeasureSpec), // getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); // 传入这个方法的参数就是组件的最终宽高 setMeasuredDimension(300, 200); } // 分配位置 // left、top、right、bottom是父空间传入的参数,这四个参数表明了组件的宽高和位置 @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // TODO Auto-generated method stub super.onLayout(changed, left, top, right, bottom); // System.out.println(left + " ; " + top + " ; " + right + " ; " + // bottom); } // 在组件中绘制内容 @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); Paint paint = new Paint(); canvas.drawColor(Color.RED); paint.setColor(Color.YELLOW); // 设置抗锯齿 paint.setAntiAlias(true); // 设置圆心坐标和圆的半径 canvas.drawCircle(100, 100, 60, paint); } }