Android进阶之自定义View实战(一)仿iOS UISwitch控件实现
来源:互联网 发布:俄国构成主义知乎 编辑:程序博客网 时间:2024/04/28 04:28
一.引言
个人觉得,自定义View一直是Android开发最变换莫测、最难掌握、最具吸引力的地方。因为它涉及到的知识点比较多,想在实际应用中驾轻就熟,由浅入深,你需要掌握以下知识点:
1. View的绘制机制以及Canvas、Paint、Rect等的常用方法;
2. View的测量及布局机制:熟悉View的测量模式以及对实际宽高的影响;熟悉对view位置的影响因素,如:layout/onLayout方法、LayoutParams、TransationX/Y。
3. View/ViewGroup的事件分发、拦截及消费机制
4. View的滚动机制:Scroller的使用
接下来的几篇博客,我会给大家分享自己项目中的典型案例,让大家明白自定义View的常用套路。这篇博客主要介绍如何通过Canvas、Paint结合属性动画实现一个高仿苹果UISwitch的控件。自定义View官网链接
二.案例分析
有关Canvas、Paint的学习资料网上有很多,这里就不再赘述了。下面直接进入主题,看看苹果上UISwitch长啥样子吧。
可以看到,按钮主要包括以下元素:
1. 跑道形状的底板
2. 可变颜色的滑槽
3. 圆形手柄
4. 底板和手柄的深色边框
这里的深色边框可以有两种方式实现:1.Path 2.绘制两个图层,二者叠加实现。方法一的主要工作在于确定跑道形状的路径,运算量较大。方法二简单粗暴,稍有不足的做了冗余的绘制。个人倾向于后者。经过分解,于是就有了绘制的流程:
1. 深色底板的绘制(这个底板颜色也就是边框的颜色);
2.灰色底板的绘制,这个底板颜色可变,size比深色底板小一个边框的宽度
3.手柄的绘制,深色圆盘和白色圆盘组成带边框的手柄
基本形状的绘制流程弄清楚之后,下面还有另外一个问题,开关切换时,手柄移动的同时,手柄槽颜色也从白色渐变到绿色,这个功能怎么实现呢?这个开关的切换过程包含了两个属性的渐变:手柄位置和手柄槽颜色,很容易让人想到属性动画,是不是?不熟悉的,请看看上篇博客:Android进阶之Property Animator研究。在切换动画的过程中,水平位移线性渐变,颜色的渐变可以通过RGB三通道的颜色渐变来实现。
绘制和平滑切换的问题解决了之后,后面的点击事件和回调处理就so easy啦?下面看看代码实现吧。
三.范例代码
AppleSwitch代码实现:
package com.star.appletogglebutton;import android.animation.ObjectAnimator;import android.animation.ValueAnimator;import android.content.Context;import android.content.res.Resources;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.util.AttributeSet;import android.util.TypedValue;import android.view.View;import android.view.animation.OvershootInterpolator;/** * Created by kakaxicm on 16/5/17. */public class AppleSwitch extends View { private final int BORDER_WIDTH = 2;//边框宽度 private int mBasePlaneColor = Color.GRAY;//地盘颜色 private int mOpenSlotColor = Color.parseColor("#4ebb7f"); private int mOffSlotColor = Color.parseColor("#dadbda");//关闭时手柄滑动槽的颜色 private int mSlotColor; private RectF mRect = new RectF(); //绘制参数 private float mBackPlaneRadius;//底板的圆形半径 private float mSpotRadius;//手柄半径 private float spotStartX;//手柄的起始X位置,切换时平移改变它 private float mSpotY;//手柄的起始X位置,不变 private float mOffSpotX;//关闭时,手柄的水平位置 private Paint mPaint;//画笔 private boolean mIsToggleOn;//开关标记 private OnToggleListener mOnToggleListener;//toggle事件监听 interface OnToggleListener{ void onSwitchChangeListener(boolean switchState); } public AppleSwitch(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if(mIsToggleOn){ toggleOff(); }else { toggleOn(); } mIsToggleOn = !mIsToggleOn; if(mOnToggleListener != null){ mOnToggleListener.onSwitchChangeListener(mIsToggleOn); } } }); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int wMode = MeasureSpec.getMode(widthMeasureSpec); int hMode = MeasureSpec.getMode(heightMeasureSpec); int wSize = MeasureSpec.getSize(widthMeasureSpec); int hSize = MeasureSpec.getSize(heightMeasureSpec); int resultWidth = wSize; int resultHeight = hSize; Resources r = Resources.getSystem(); //lp = wrapcontent时 指定默认值 if(wMode == MeasureSpec.AT_MOST){ resultWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, r.getDisplayMetrics()); } if(hMode == MeasureSpec.AT_MOST){ resultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, r.getDisplayMetrics()); } setMeasuredDimension(resultWidth, resultHeight); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { mBackPlaneRadius = Math.min(getWidth(), getHeight()) * 0.5f; mSpotRadius = mBackPlaneRadius - BORDER_WIDTH; spotStartX = 0; mSpotY = 0; mOffSpotX = getMeasuredWidth() - mBackPlaneRadius*2; mSlotColor = mOffSlotColor; } @Override protected void onDraw(Canvas canvas) { //画底板 mRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); mPaint.setColor(mBasePlaneColor); canvas.drawRoundRect(mRect, mBackPlaneRadius, mBackPlaneRadius, mPaint); //画手柄的槽 mRect.set(BORDER_WIDTH, BORDER_WIDTH, getMeasuredWidth() - BORDER_WIDTH, getMeasuredHeight() - BORDER_WIDTH); mPaint.setColor(mSlotColor); canvas.drawRoundRect(mRect, mSpotRadius, mSpotRadius, mPaint); //手柄包括包括两部分,深色底板和白板,这样做的目的是使圆盘具有边框 //手柄的底盘 mRect.set(spotStartX, mSpotY, spotStartX+mBackPlaneRadius*2, mSpotY+mBackPlaneRadius*2); mPaint.setColor(mBasePlaneColor); canvas.drawRoundRect(mRect, mBackPlaneRadius, mBackPlaneRadius, mPaint); //手柄的圆板 mRect.set(spotStartX+BORDER_WIDTH, mSpotY+BORDER_WIDTH, mSpotRadius*2+spotStartX+BORDER_WIDTH, mSpotRadius*2+mSpotY+BORDER_WIDTH); mPaint.setColor(Color.WHITE); canvas.drawRoundRect(mRect, mSpotRadius, mSpotRadius, mPaint); } public float getSpotStartX() { return spotStartX; } public void setSpotStartX(float spotStartX) { this.spotStartX = spotStartX; } /** * 计算切换时的手柄槽的颜色 * @param fraction 动画播放进度 * @param startColor 起始颜色 * @param endColor 终止颜色 */ public void calculateColor(float fraction, int startColor, int endColor){ final int fb = Color.blue(startColor); final int fr = Color.red(startColor); final int fg = Color.green(startColor); final int tb = Color.blue(endColor); final int tr = Color.red(endColor); final int tg = Color.green(endColor); //RGB三通道线性渐变 int sr = (int) (fr + fraction*(tr - fr)); int sg = (int) (fg + fraction*(tg - fg)); int sb = (int) (fb + fraction*(tb - fb)); //范围限定 sb = clamp(sb, 0, 255); sr = clamp(sr, 0, 255); sg = clamp(sg, 0, 255); mSlotColor = Color.rgb(sr, sg, sb); } private int clamp(int value, int low, int high) { return Math.min(Math.max(value, low), high); } //关闭 public void toggleOn(){ //手柄槽颜色渐变和手柄滑动通过属性动画来实现 ObjectAnimator animator = ObjectAnimator.ofFloat(this,"spotStartX", 0, mOffSpotX); animator.setDuration(300); animator.start(); animator.setInterpolator(new OvershootInterpolator(0.5f)); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float fraction = animation.getAnimatedFraction(); calculateColor(fraction, mOffSlotColor, mOpenSlotColor); invalidate(); } }); } //打开 public void toggleOff(){ //手柄槽颜色渐变和手柄滑动通过属性动画来实现 ObjectAnimator animator = ObjectAnimator.ofFloat(this,"spotStartX",mOffSpotX, 0); animator.setDuration(300); animator.start(); animator.setInterpolator(new OvershootInterpolator(0.5f)); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float fraction = animation.getAnimatedFraction(); calculateColor(fraction, mOpenSlotColor, mOffSlotColor); invalidate(); } }); } public boolean getSwitchState(){ return mIsToggleOn; } public void setToggle(boolean state){ mIsToggleOn = state; if(mIsToggleOn){ toggleOff(); }else { toggleOn(); } } public void setOnToggleListener(OnToggleListener listener){ mOnToggleListener = listener; }}
说明:
1.在onDraw方法里面的边框可以用绘制Path代替,绘制圆形也可以直接用drawCircle方法代替,看个人喜好
2.onMeasure方法里面设定了默认的size,否则,在它和父View的LayoutParams均配置为wrap_content时,会充满父view。这里的机制会在后面的博客详细说明.
3.下面的代码
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float fraction = animation.getAnimatedFraction(); calculateColor(fraction, mOffSlotColor, mOpenSlotColor); invalidate(); } });
实现了手柄位置和槽的颜色渐变,当然也可以在setSpotStartX中实现,只是颜色渐变所需要的参量fraction还要计算,所以就放在动画监听里了。
4.calculateColor方法通过RGB三通道颜色渐变与合成来实现颜色的平滑渐变
5.自定义属性网上资料也有很多,这里写死了开关状态的颜色。
通过这个实例,希望能让大家觉得:原来一些简单的控件也是just so so嘛!
- Android进阶之自定义View实战(一)仿iOS UISwitch控件实现
- Android自定义View仿IOS选择控件Togglebutton实现
- iOS每日一记之----------------自定义UIswitch 控件
- <Android 进阶(四)> 自定义View之仿Emui开关控件
- android 开发进阶 自定义控件-仿ios自动清除控件
- Android自定义View之popupwindow进阶封装:高仿ios “item动画弹出”效果的popupwindow。
- Android进阶之自定义view(一)
- 【Android进阶之自定义View(一)】
- Android自定义控件之自定义View(一)
- Android自定义控件实战——实现仿IOS下拉刷新上拉加载 PullToRefreshLayout
- Android进阶——自定义View之重写ViewGroup组合系统控件实现自定义ToolBar模板
- iOS 控件之 UISwitch
- Android小白进阶(三)--自定义控件之自定义View
- IOS之自定义UISwitch
- IOS之自定义UISwitch
- Android进阶之自定义控件一
- Android进阶之自定义View实战(二)九宫格手势解锁实现
- Android进阶之自定义View实战(三)贝塞尔曲线应用
- COE文件格式
- JavaScript和JQuery获取select的值
- STL容器总结之list
- Apache+tomcat的整合
- opencv2.4.9在vs2013中丢失opencv_core249d.dll问题及解决【转】
- Android进阶之自定义View实战(一)仿iOS UISwitch控件实现
- STL容器总结之deque
- 内存分析工具MAT的使用
- 单调队列或单调栈的学习及认识
- STL容器总结之stack和queue
- mvc控制器的action参数问题
- POJ-1308-Is It A Tree?(并查集 判断树)
- 基础级 - sweetAlert
- docker