一、效果展示
二、相关链接
Android自定义控件系列二:自定义开关按钮(一)
Android自定义控件系列三:自定义开关按钮(二)
Android自定义控件系列三:自定义开关按钮(三)— 自定义属性
三、自定义View的具体步骤
1、首先需要写一个类来继承自View(或者View的子类)
2、需要得到view的对象(并初始化变量),那么需要重写构造方法,其中一参的构造方法用于new,二参的构造方法用于xml布局文件使用,三参的构造方法可以传入一个样式。
3、需要设置view的大小,那么需要重写onMeasure方法
4、需要设置view的位置,那么需要重写onLayout方法,但是这个方法在自定义view的时候用的不多,原因主要在于view的位置主要是由父控件来决定
5、需要绘制出所需要显示的view,那么需要重写onDraw方法
6、当控件状态改变的时候,需要重绘view,那么调用invalidate();方法,这个方法实际上会重新调用onDraw方法
7、在这其中,如果需要对view设置点击事件,可以直接调用setOnClickListener方法
8、需要实现触摸拖拽功能,那么需要重写的onTouchEvent方法,基本上是处理ACTION_DOWN、ACTION_MOVE和ACTION_UP事件。
8、在布局中使用此自定义控件
四、自定义开关按钮的思路
1、首先需要定义一个类,继承自View;
2、定义两参数构造方法,在构造函数中初始化视图,在初始化中:获取背景图和按钮转化为bitmap对象,计算最大偏移量=背景图宽 - 按钮宽()。
3、在onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法的内部调用setMeasuredDimension(mBackgroudBitmap.getWidth(), mBackgroudBitmap.getHeight());指定背景如何显示
4、(最关键)对于一个控件需要显示,需要将它绘制出来,这里就需要重写onDraw方法,来将这个控件绘制出。
// 画笔工具
Paint paint = new Paint();
// 初始化一个画笔
paint = new Paint();
// 设置抗锯齿
paint.setAntiAlias(true);
// 1.画出背景图片
canvas.drawBitmap(mBackgroudBitmap, 0, 0, paint);
// 2.画出button按钮(开始位置:当前偏移量,实时更新)
canvas.drawBitmap(mSlidingBitmap, mSlidingCurrentOffset, 0, paint);
5、当控件状态改变的时候,需要刷新view的显示状态,就调用invalidate()方法,这个方法实际上会重新调用onDraw方法来重绘控
6、在定义控件的过程中,如果需要对view设置点击事件,可以直接使用setOnClickListener方法,而不需要写view.setOnClickListener;
onClick和onLongClick是在super.onTouchEvent方法里被调用的,onClick是在ACTION_UP的时候可能被调用,而onLongClick是在ACTION_DOWN的时候可能被调用。
7、需要实现触摸拖拽功能,重写onTouchEvent方法,基本上是处理ACTION_DOWN、ACTION_MOVE和ACTION_UP事件。
/** * 触摸监听 * **关键步骤:1.2.3.4.** * @param event * @return */
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 1.记录第一次触摸时按下的X坐标
mStartX = event.getX();
// 按下时恢复开关的点击事件
mIsClickable = true;
break;
case MotionEvent.ACTION_MOVE:
mMoveX = event.getX();
// 2.1如果有移动距离,就视为是触摸,就屏蔽点击事件
if (Math.abs(mMoveX - mStartX) > 0) {
mIsClickable = false;
}
// 2.2如果有移动距离,得到最新的当前距离
mSlidingCurrentOffset += (int) (mMoveX - mStartX);
if (mSlidingCurrentOffset > mSlidingMaxOffset)
mSlidingCurrentOffset = mSlidingMaxOffset;
if (mSlidingCurrentOffset < 0)
mSlidingCurrentOffset = 0;
// 3.重新初始化界面
flushView();
// 4.还原,最新的开始X坐标
mStartX = event.getX();
break;
case MotionEvent.ACTION_UP:
// 抬起的时候,判断松开的位置是哪,来决定开关的状态是打开还是关闭
if (mSlidingCurrentOffset > mSlidingMaxOffset / 2)
mSlidingCurrentOffset = mSlidingMaxOffset;
if (mSlidingCurrentOffset <= mSlidingMaxOffset / 2)
mSlidingCurrentOffset = 0;
// 重新初始化界面
flushView();
break;
}
invalidate();
return true;
}
8、在布局文件中将这个自定义控件定义出来,注意名字要使用全类名;
五、源码
1.activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.guan.custombutton.CustomButton
android:id="@+id/customButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>
2.CustomButton.java
/** * @author Guan * @file com.guan.custombutton * @date 2015/11/10 * @Version 1.0 */
public class CustomButton extends View implements View.OnClickListener {
// 背景图片
private Bitmap mBackgroudBitmap;
// 开关图片
private Bitmap mSlidingBitmap;
// 开关是否打开
private boolean mIsOpen;
// 是否可点击
private boolean mIsClickable;
// 最大偏移量(离控件左边的距离)
private int mSlidingMaxOffset;
// 当前偏移量
private int mSlidingCurrentOffset;
// 开始X坐标
private float mStartX;
// 移动的X坐标
private float mMoveX;
public CustomButton(Context context) {
super(context);
}
/** * 构造方法(两个参数,必须重写) * * @param context * @param attrs */
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
/** * 初始化 */
private void initView() {
// 初始化一个画笔
mPaint = new Paint();
// 设置抗锯齿
mPaint.setAntiAlias(true);
// 得到背景图片转化为bitmap对象
mBackgroudBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.a);
// 得到button图片转化为bitmap对象
mSlidingBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.b);
// 计算最大偏移量(=背景图宽 - 按钮宽)
mSlidingMaxOffset = mBackgroudBitmap.getWidth() - mSlidingBitmap.getWidth();
// 给自定义的view设置点击事件
setOnClickListener(this);
}
/** * 测量指定图片如何显示(如何展示给用户) * * @param widthMeasureSpec * @param heightMeasureSpec */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 指定背景如何显示
setMeasuredDimension(mBackgroudBitmap.getWidth(), mBackgroudBitmap.getHeight());
}
/** * @param canvas */
@Override
public void onDraw(Canvas canvas) {
// 画笔工具
Paint paint = new Paint();
// 初始化一个画笔
paint = new Paint();
// 设置抗锯齿
paint.setAntiAlias(true);
// 1.画出背景图片
canvas.drawBitmap(mBackgroudBitmap, 0, 0, mPaint);
// 2.画出button按钮(开始位置:当前偏移量,实时更新)
canvas.drawBitmap(mSlidingBitmap, mSlidingCurrentOffset, 0, mPaint);
}
/** * 给自定义的view设置点击事件 * * onClick和onLongClick是在super.onTouchEvent方法里被调用的,onClick是在ACTION_UP的时候可能被调用,而onLongClick是在ACTION_DOWN的时候可能被调用。 */
@Override
public void onClick(View v) {
if (mIsClickable) {
// 如何是打开的
if (mIsOpen) {
mSlidingCurrentOffset = 0;
} else {
mSlidingCurrentOffset = mSlidingMaxOffset;
}
mIsOpen = !mIsOpen;
// 重新初始化界面,可是OnDraw再次执行
flushView();
}
}
/** * 触摸监听 * * @param event * @return */
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 1.记录第一次触摸时按下的X坐标
mStartX = event.getX();
// 按下时恢复开关的点击事件
mIsClickable = true;
break;
case MotionEvent.ACTION_MOVE:
mMoveX = event.getX();
// 如果有移动距离,就视为是触摸,就屏蔽点击事件
if (Math.abs(mMoveX - mStartX) > 0) {
mIsClickable = false;
}
// 得到最新的当前距离
mSlidingCurrentOffset += (int) (mMoveX - mStartX);
if (mSlidingCurrentOffset > mSlidingMaxOffset)
mSlidingCurrentOffset = mSlidingMaxOffset;
if (mSlidingCurrentOffset < 0)
mSlidingCurrentOffset = 0;
// 重新初始化界面
flushView();
// 还原,最新的开始X坐标
mStartX = event.getX();
break;
case MotionEvent.ACTION_UP:
// 抬起的时候,判断松开的位置是哪,来决定开关的状态是打开还是关闭
if (mSlidingCurrentOffset > mSlidingMaxOffset / 2)
mSlidingCurrentOffset = mSlidingMaxOffset;
if (mSlidingCurrentOffset <= mSlidingMaxOffset / 2)
mSlidingCurrentOffset = 0;
// 重新初始化界面
flushView();
break;
}
invalidate();
return true;
}
/** * 刷新视图 */
protected void flushView() {
// 刷新当前view会导致ondraw方法的执行
invalidate();
}
}