一步一步实现自定义控件(四)
来源:互联网 发布:淘宝ins是什么品牌 编辑:程序博客网 时间:2024/05/16 01:18
前言
今天我们要实现的是是一个自定义开关,之前都是集成已有的控件,今天这个却有些不太一样,这个是继承View的,也就真的是完全自定义控件了
效果图
实现
- 首先写一个类继承View,重写三个构造方法
public class ToggleView extends View {public ToggleView(Context context) { super(context); } public ToggleView(Context context, AttributeSet attrs) { super(context, attrs); } public ToggleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }}
OK,解释下这三个构造方法:
- 第一个用于代码创建控件
- 第二个用于xml中使用,可指定自定义属性
- 第三个用于xml中使用,可指定自定义属性,如果指定了样式,则走此构造函数
接下来先简单说下Android的绘制流程,大体说下:
Android绘制控件在界面打开时,生命周期onResume()之后
measure -> layout -> draw
对应的就是 测量 -> 摆放 ->绘制
对应的方法就是 onMeasure -> onLayout -> onDraw重写这些方法,实现自定义控件
其中:
View只需要两个方法:
onMeasure()(指定自己的宽高) -> onDraw()(绘制自己的内容)
ViewGroup需要三个方法:
onMeasure()(指定自己的宽高,所有子View的宽高) ->onLayout()(摆放所有子View) -> onDraw()(绘制自己的内容)
OK,之后我们把我们自定义的控件先添加到住布局中,这里注意,要全路经带包名的
<com.example.hfs.toggleview.ui.ToggleView android:id="@+id/toggle" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
然后找到它,并且给它设置一些属性,没有的方法我们在我们自定义控件中添加旧OK了
mToggleView = (ToggleView) findViewById(R.id.toggle); //设置背景 mToggleView.setSwitchBackgroundResource(R.mipmap.switch_background); //设置滑块背景 mToggleView.setSlideButtonResource(R.mipmap.slide_button); //设置开关状态 mToggleView.setSwitchState(false);
设置背景了,我们就可以把它转成一个Bitmap对象
//设置背景 public void setSwitchBackgroundResource(int background) { mSwitchBackgroundResouce = BitmapFactory.decodeResource(getResources(),background); } //设置滑块背景public void setSlideButtonResource(int button) { mSlideButtonBitmap = BitmapFactory.decodeResource(getResources(),button); }
接下来我们就要重写那两个方法了onMeasure()和onDraw()
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); }
这里我们只需要显示我们开关的宽高就可以了,所以我们得修改下onMeasure的返回值,返回刚才我们设置的背景的宽高就行了
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(mSwitchBackgroundResouce.getWidth(),mSwitchBackgroundResouce.getHeight()); }
接下里就到onDraw方法了,Canvas 是画布的意思,我们通过它就可以绘制出我们想要的图形,油画布了还需要一只画笔,画笔可以共用,所以我们在每个构造方法都加一个init()方法,初始化画笔,同时以后也要初始化一些其它的东西
private void init() { mPaint = new Paint(); }
有了画布和画笔就开始绘制我们的控件了:
第一步:画背景
canvas.drawBitmap(mSwitchBackgroundResouce,0,0,mPaint);
第二步:画滑块
这步的注意下,因为开关的滑块不是固定的,开关有两种状态,在不同的状态下,滑块的位置是不同的,同样画的也是不同的,这个状态值就是我们之前传过来的boolean值,接下来我们就要通过这个值来进行绘制
public void setSwitchState(boolean state) { this.mSwitchState=state;//给状态值赋值 }
有了状态值就开始画滑块:
//根据开关状态直接设置图片位置if (mSwitchState){ int newLeft=mSwitchBackgroundResouce.getWidth()-mSlideButtonBitmap.getWidth();//获取左边的坐标 canvas.drawBitmap(mSlideButtonBitmap,newLeft,0,mPaint); }else{ canvas.drawBitmap(mSlideButtonBitmap,0,0,mPaint); }
OK,控件基本已经画出来了,现在我们点击它还不会动,因为缺少触摸事件的监听,所以接下来我们就要给它设置触摸监听,因为我们是自定义的控件,我们就直接在我们的自定义控件内重写方法了,外面不需要管里面是怎么实现的
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: currentX = event.getX();//获取当前位置 break; case MotionEvent.ACTION_MOVE: currentX = event.getX();//获取当前位置 break; case MotionEvent.ACTION_UP: currentX = event.getX();//获取当前位置 break; } return true; }
我们之前都是都过一个boolean值来画滑块的,但是实际中我们却要根据用户的滑动位置来画滑块,所以这里我们要设置一个boolean值,表示当前是否是滑动状态,我们按下去的时候置为true,表示滑动状态,松手置为false,表示不是滑动状态了,同时还要重绘界面,当我们处在滑动状态时,我们就按照滑动状态绘制滑块;当我们不在滑动状态时,我们还是要按照之前开关状态绘制滑块
说下重绘界面方法:
// 会引发onDraw()被调用, 里边的变量会重新生效.界面会更新invalidate();
贴下完整的onDraw()方法:
// Canvas 画布, 画板. 在上边绘制的内容都会显示到界面上. @Override protected void onDraw(Canvas canvas) { // 1. 绘制背景 canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint); // 2. 绘制滑块 if(isTouchMode){ // 根据当前用户触摸到的位置画滑块 // 让滑块向左移动自身一半大小的位置 float newLeft = currentX - slideButtonBitmap.getWidth() / 2.0f; int maxLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth(); // 限定滑块范围 if(newLeft < 0){ newLeft = 0; // 左边范围 }else if (newLeft > maxLeft) { newLeft = maxLeft; // 右边范围 } canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint); }else { // 根据开关状态boolean, 直接设置图片位置 if(mSwitchState){// 开 int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth(); canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint); }else {// 关 canvas.drawBitmap(slideButtonBitmap, 0, 0, paint); } } } boolean isTouchMode = false; // 重写触摸事件, 响应用户的触摸. @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isTouchMode = true; currentX = event.getX(); break; case MotionEvent.ACTION_MOVE: currentX = event.getX(); break; case MotionEvent.ACTION_UP: isTouchMode = false; currentX = event.getX(); float center = switchBackgroupBitmap.getWidth() / 2.0f; // 根据当前按下的位置, 和控件中心的位置进行比较. boolean state = currentX > center; break; default: break; } // 重绘界面 invalidate(); // 会引发onDraw()被调用, 里边的变量会重新生效.界面会更新 return true; // 消费了用户的触摸事件, 才可以收到其他的事件. }
OK,接下来我么还差一个接口监听,类似于View的setOnClickListener一样,当我们的开关状态改变时我们得需要通知外面,这里就是通过接口回调来实现的
首先,在我们自定义的类中定义一个接口,同时提供set方法
public interface OnSwitchStateUpdateListener{ // 状态回调, 把当前状态传出去 void onStateUpdate(boolean state); } public void setOnSwitchStateUpdateListener( OnSwitchStateUpdateListener onSwitchStateUpdateListener) { this.onSwitchStateUpdateListener = onSwitchStateUpdateListener; }
OK,那么我们在哪里调用呢?没错,就是在我们松开手指的时候,我们就要把状态值传出去:
case MotionEvent.ACTION_UP: isTouchMode = false; System.out.println("event: ACTION_UP: " + event.getX()); currentX = event.getX(); float center = switchBackgroupBitmap.getWidth() / 2.0f; // 根据当前按下的位置, 和控件中心的位置进行比较. boolean state = currentX > center; // 如果开关状态变化了, 通知界面. 里边开关状态更新了. if(state != mSwitchState && onSwitchStateUpdateListener != null){ // 把最新的boolean, 状态传出去了 onSwitchStateUpdateListener.onStateUpdate(state); } mSwitchState = state; break;
OK,这样我们就可以在MainActivity中拿到这个状态值了
// 设置开关更新监听 mToggleView.setOnSwitchStateUpdateListener(new OnSwitchStateUpdateListener(){ @Override public void onStateUpdate(boolean state) { Toast.makeText(getApplicationContext(), "state: " + state, 0).show(); } });
好了,这样看来整体上我们的这个控件好像写完了,其实没有,因为一般自定义控件设置属性都不会在代码中设置的,就是MainActivity中那几行代码,一般都是在xml中配置这些,所以我们也要这么做.
首先,我们要在res目录下的values目录下新建一个attrs.xml,之后我们就可以声明我们的自定义属性了
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="ToggleView"> <attr name="switch_background" format="reference" /> <attr name="slide_button" format="reference" /> <attr name="switch_state" format="boolean" /> </declare-styleable></resources>
这里有兴趣的童鞋可以看下Android的一些源码,比如View的,为什么有些控件我们在xml中能直接使用一些属性,因为Android已经帮我们定义好了一些属性,定义的格式和我们上面那段一样的
OK,声明完我们需要的属性后,我们就可以在xml中使用了,怎么使用呢?首先需要我们自己命名一个命名空间,名字随意,然后就可以使用了:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.hfs.toggleview.MainActivity"> <com.example.hfs.toggleview.ui.ToggleView android:id="@+id/toggle" android:layout_centerInParent="true" app:switch_background="@mipmap/switch_background" app:slide_button="@mipmap/slide_button" app:switch_state="false" android:layout_width="wrap_content" android:layout_height="wrap_content"/></RelativeLayout>
这里的app就是我们自己的命名空间
这样我们就可以把那三行代码删除了,同时也可以删除我们自定义类中对应的方法了,于是MainActivity代码就很简单了
public class MainActivity extends AppCompatActivity { private ToggleView mToggleView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mToggleView = (ToggleView) findViewById(R.id.toggle); mToggleView.setOnSwitchStateUpdateListener(new ToggleView.onSwitchStateUpdateListener() { @Override public void onStateUpdate(boolean state) { if (state) { Toast.makeText(MainActivity.this, "开关打开", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "开关关闭", Toast.LENGTH_SHORT).show(); } } }); }}
OK,这样我们的一个简单自定义开关就写完了,这只是个例子,起到抛砖引用作用
源码:
https://github.com/Greathfs/ToggleView
- 一步一步实现自定义控件(四)
- 一步一步实现自定义控件(一)
- 一步一步实现自定义控件(二)
- 一步一步实现自定义控件(三)
- 一步一步走向自定义控件
- C#自定义控件一步一步走
- 安卓自定义控件(四)实现自定义Layout
- 自定义控件四
- Android自定义“图片+文字”控件四种实现方法
- Android自定义控件:进度条的四种实现方式
- Android自定义控件:进度条的四种实现方式
- Android自定义控件:进度条的四种实现方式
- 一步一步实现Android自定义组合View
- 一步一步学android控件(之四) —— EditText
- 简单的自定义控件四简单的自定义控件四
- 自定义控件自学笔记(四)
- 一步一步学习ObjectDataSource控件--自定义分页排序
- 自定义控件实战<四> 音量增减控件
- iOS开发陷阱之NSString - compare
- jQuery.unbind()
- 第61篇Chrome扩展蓝牙开发(一)
- android开发笔记之adb shell dumpsys
- Core Services 层
- 一步一步实现自定义控件(四)
- 剑指Offer面试题37(Java版):两个链表的第一个公共结点
- contiki概述
- Chapter 2 匹配边界及选择
- 全卷积网络(FCN)
- Contiki学习——HelloWorld
- BZOJ2794/POI2012 Cloakroom
- hdu5918Sequence I+KMP
- 练习