由于项目中须要使用开关切换button,和声音滑动控件,可是原生Android5.0版本号以下的控件实在是太挫了。尽管网上已经有非常多关于这两个控件的blog。可是我实在是找不到像iPhone这样简洁样式的,只是咱们程序猿总不能这点问题就被难道撒···所以我决定仿照iphone的样式自己写这两个控件,。

效果图例如以下:

一、ToggleButton

先直接上代码。后面会一步步分析

package com.zuck.definitionview.views;import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.OvershootInterpolator;/*** 仿iPhone ToggleButton* * 2015-7-13* * @author zuck**/
public class ToggleButton extends View{private float radius;// 开启颜色private int onColor;// 关闭颜色private int offColor;// 灰色带颜色private int offBorderColor;// 手柄颜色private int spotColor;// 边框颜色private int borderColor;// 画笔private Paint paint ;// 开关状态private boolean toggleOn = false;// 边框大小 默觉得2pxprivate int borderWidth = 2;// 垂直中心private float centerY;// button的開始和结束位置private float startX, endX;// 手柄X位置的最小和最大值private float spotMinX, spotMaxX;// 手柄大小private int spotSize ;///  手柄X位置private float spotX;// 关闭时内部灰色带高度private float offLineWidth;private RectF rect = new RectF();//开关切换监听器private OnToggleChanged listener;//属性动画private ValueAnimator animation;public ToggleButton(Context context) {this(context, null);}public ToggleButton(Context context, AttributeSet attrs) {this(context, attrs, 0);}public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}public void init() {initPaint();initColor();initAnimation();this.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View arg0) {toggle();}});}private void initPaint() {//初始化画笔(抗抖动)paint = new Paint(Paint.ANTI_ALIAS_FLAG);//绘制风格为填充paint.setStyle(Style.FILL);//笔触风格为圆角paint.setStrokeCap(Cap.ROUND);}private void initColor() {onColor = Color.parseColor("#4ebb7f");offColor = Color.parseColor("#dadbda");offBorderColor = Color.parseColor("#ffffff");spotColor = Color.parseColor("#ffffff");//由于開始为关闭状态。所以这里边框背景色初始化为关闭状态颜色borderColor = offColor;}@SuppressLint("NewApi")private void initAnimation() {animation = new ValueAnimator();animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {calculateToggleEffect(Double.parseDouble(animation.getAnimatedValue().toString()));}});//OvershootInterpolator : 结束时会超过给定数值,可是最后一定返回给定值animation.setInterpolator(new OvershootInterpolator(1.5f)); animation.setDuration(500);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {final int widthMode = MeasureSpec.getMode(widthMeasureSpec);final int heightMode = MeasureSpec.getMode(heightMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);Resources r = Resources.getSystem();if(widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST){widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 55, r.getDisplayMetrics());widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);}if(heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST){heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, r.getDisplayMetrics());heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);}super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);final int width = getWidth();final int height = getHeight();//由宽高计算圆角的半径radius = Math.min(width, height) * 0.5f;centerY = radius;startX = radius;endX = width - radius;spotMinX = startX + borderWidth;spotMaxX = endX - borderWidth;spotSize = height - 4 * borderWidth;spotX = toggleOn ?

spotMaxX : spotMinX; offLineWidth = 0; } private int clamp(int value, int low, int high) { return Math.min(Math.max(value, low), high); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1.绘制最外层边框背景-圆角矩形 //绘制圆角矩形背景大小为測量的宽高 rect.set(0, 0, getWidth(), getHeight()); paint.setColor(borderColor); canvas.drawRoundRect(rect, radius, radius, paint); if(offLineWidth > 0){ //1.1绘制整个开关区域中除手柄外的白色区域带 final float cy = offLineWidth * 0.5f; rect.set(spotX - cy, centerY - cy, endX + cy, centerY + cy); paint.setColor(offBorderColor); canvas.drawRoundRect(rect, cy, cy, paint); } //2.绘制圆形手柄边框区域 rect.set(spotX - 1 - radius, centerY - radius, spotX + 1.1f + radius, centerY + radius); paint.setColor(borderColor); canvas.drawRoundRect(rect, radius, radius, paint); //3.绘制圆形手柄区域 //圆形手柄的半径大小(不包括边框) final float spotR = spotSize * 0.5f; rect.set(spotX - spotR, centerY - spotR, spotX + spotR, centerY + spotR); paint.setColor(spotColor); canvas.drawRoundRect(rect, spotR, spotR, paint); } private double mapValueFromRangeToRange(double value, double fromLow, double fromHigh, double toLow, double toHigh) { double fromRangeSize = fromHigh - fromLow; double toRangeSize = toHigh - toLow; double valueScale = (value - fromLow) / fromRangeSize; return toLow + (valueScale * toRangeSize); } /** * @param value */ private void calculateToggleEffect(final double value) { final float mapToggleX = (float) mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX); spotX = mapToggleX; float mapOffLineWidth = (float) mapValueFromRangeToRange(1 - value, 0, 1, 10, spotSize); offLineWidth = mapOffLineWidth; //开启时候的背景色 final int fr = Color.red(onColor); final int fg = Color.green(onColor); final int fb = Color.blue(onColor); //关闭后的背景色 final int tr = Color.red(offColor); final int tg = Color.green(offColor); final int tb = Color.blue(offColor); //border颜色渐变 int sr = (int) mapValueFromRangeToRange(1 - value, 0, 1, fr, tr); int sg = (int) mapValueFromRangeToRange(1 - value, 0, 1, fg, tg); int sb = (int) mapValueFromRangeToRange(1 - value, 0, 1, fb, tb); sr = clamp(sr, 0, 255); sg = clamp(sg, 0, 255); sb = clamp(sb, 0, 255); borderColor = Color.rgb(sr, sg, sb); //重绘 if (Looper.myLooper() == Looper.getMainLooper()) { invalidate(); } else { postInvalidate(); } } @SuppressLint("NewApi") private void takeToggleAction(boolean isOn){ if(isOn) { animation.setFloatValues(0.f, 1.f); } else { animation.setFloatValues(1.f, 0.f); } animation.start(); } /** * 切换开关 */ public void toggle() { toggleOn = !toggleOn; takeToggleAction(toggleOn); if(listener != null){//触发toggle事件 listener.onToggle(toggleOn); } } /** * 切换为打开状态 */ public void toggleOn() { toggleOn = true; takeToggleAction(toggleOn); if(listener != null){//触发toggle事件 listener.onToggle(toggleOn); } } /** * 切换为关闭状态 */ public void toggleOff() { toggleOn = false; takeToggleAction(toggleOn); if(listener != null){//触发toggle事件 listener.onToggle(toggleOn); } } /** * 设置显示成打开样式。不会触发toggle事件 */ public void setToggleOn(){ toggleOn = true; calculateToggleEffect(1.0f); } /** * 设置显示成关闭样式。不会触发toggle事件 */ public void setToggleOff() { toggleOn = false; calculateToggleEffect(0.0f); } /** * 状态切换监听器 */ public interface OnToggleChanged{ public void onToggle(boolean on); } public void setOnToggleChanged(OnToggleChanged onToggleChanged) { listener = onToggleChanged; } }

这个控件总体看来,还是比較简单的,開始我们仍然是初始化一些须要用到的画笔、颜色、以及属性动画。

这里画笔和颜色就不说了,跟切白菜一样。
简单说说动画:为什么要用到属性动画,大家能够细致看看上面的效果图,当点击控件的时候手柄会从最左端移动到最右端,完毕一个打开的效果。

或者从最右端移动到最左端。完毕一个关闭的效果。

而且在手柄位移到最左端或者最右端的时候还会有一个回弹的效果。

而这些所有都是依据属性动画

    private void initAnimation() {animation = new ValueAnimator();animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {calculateToggleEffect(Double.parseDouble(animation.getAnimatedValue().toString()));}});//OvershootInterpolator : 结束时会超过给定数值,可是最后一定返回给定值animation.setInterpolator(new OvershootInterpolator(1.5f)); animation.setDuration(500);}

这里属性动画大家应该都非常熟悉了,假设还有不懂他的基本使用方法的。能够去这个链接去学习下关于属性动画的使用:Android属性动画全然解析(上)。初识属性动画的基本使用方法。如今我们仅仅看OvershootInterpolator这个API。

从上图能够看到OvershootInterpolator继承BaseInterpolator。那么Interpolator是什么?引用郭霖blog中的一句话:“Interpolator这个东西非常难进行翻译。直译过来的话是补间器的意思,它的主要作用是能够控制动画的变化速率”。那么按我的理解就是控制view动画效果的节奏。
从这个继承关系图我们能够看到,Android为我们提供了非常多Interpolator,有兴趣的童鞋能够挨个试一试。

看看他们的效果。这里带大家看看OvershootInterpolator:

/**
* An interpolator where the change flings forward and overshoots the last value
* then comes back.
*/

这是API中关于OvershootInterpolator的介绍。我的英文稀烂,大致意思是:值在变化时,快结束的时候值会超过给定的最大值,然后再返回给定的最大值。也就是说,当我们有例如以下设置的时候

animation.setFloatValues(0.f, 1.f);

onAnimationUpdate()回调方法最后会按 0.9f->1.0f->1.1f->1.2f->….1.0f。这种方式返回数值。

通过这种数值去控制手柄的执行轨迹从而达到一个回弹的效果。

另一点:在我创建OvershootInterpolator的时候传入一个tension參数,tension表示张力。默觉得2.0f。tension越大,那么最后超过给定最大边界值后继续增大数值也就越大,反之越小。

ok,以上就是关于初始化操作。

以下也就是按流程一步一步看了:onMeasure() -> onLayout() -> onDraw();
这些都是老生长谈了:

onMeasure

在onMeasure方法中去測量控件的大小这块逻辑非常easy。

这里仅仅处理了AT_MOST和UNSPECIFIED两种模式。当我们把宽度或者高度设置为wrap_content或者设置了weight,那么相应我们让ToggleButton的长宽也为默认值。

其他方式。測量方法中不做干涉。

用户设置的宽高是多少,那么控件的宽高就是多少。

onLayout

在onLayout方法中去计算了该控件在绘制的时候须要用到的几个參数,比如包括边框的手柄半径、手柄的開始位置、结束位置、手柄大小等等…看看也没啥好说的,都是一系列计算。

onDraw

最后看onDraw方法。控件的绘制主要分为四步,上述代码中有明确的凝视:

1.绘制控件最外层边框,这里注意这个边框是圆矩形,他们的rx 和ry的值也就是在onLayout中计算的radius值
2. 绘制圆角矩形内除去手柄区域以外的区域,该区域的大小是由offLineWidth去控制的,而关于offLineWidth是怎么算出来的,等下会跟大家讲明确的
3. 绘制手柄的边框区域。该区域的位置是由手柄的位置spotX去控制的。而spotX的计算,等下和offLineWidth的计算一并跟大家说明确(ps:为什么要一起,由于计算他们两个的值都是用的同一种算法)
4. 绘制手柄区域和绘制手柄边框区域差点儿是一致的,唯一不同的是绘制手柄区域的半径大小比绘制手柄边框区域的大小要小2个单位值。由于他们绘制的位置是一样的,而大小不一样,这样也就让整个手柄有了边框的效果同一时候看起来更加有扁平感了。

同一时候该手柄区域的位置也是由spotX去控制的

到此从init() 到 onMeasure() -> onLayout() -> onDraw()这些方法逻辑思路在大家心中应该是比較清晰明了的。

calculateToggleEffect -> mapValueFromRangeToRange

以下就来说说该控件中最重要的算法问题了,事实上这个算法也是相当的简单
当我们点击控件。内部是如何驱动手柄左右位移的呢,圆矩形内颜色是如何转变的呢?这一切要归功于calculateToggleEffect()这种方法,上面讲述为什么要使用属性动画的时候。我曾贴过一小段代码,在onAnimationUpdate()回调方法中我们调用了calculateToggleEffect()方法,依据属性动画回传给我们的值去计算控制位移、颜色变化等效果的三个參数值

    private void calculateToggleEffect(final double value) {final float mapToggleX = (float) mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX);spotX = mapToggleX;float mapOffLineWidth = (float) mapValueFromRangeToRange(1 - value, 0, 1, 10, spotSize);offLineWidth = mapOffLineWidth;//开启时候的背景色final int fr = Color.red(onColor);final int fg = Color.green(onColor);final int fb = Color.blue(onColor);//关闭后的背景色final int tr = Color.red(offColor);final int tg = Color.green(offColor);final int tb = Color.blue(offColor);//border颜色渐变int sr = (int) mapValueFromRangeToRange(1 - value, 0, 1, fr, tr);int sg = (int) mapValueFromRangeToRange(1 - value, 0, 1, fg, tg);int sb = (int) mapValueFromRangeToRange(1 - value, 0, 1, fb, tb);sr = clamp(sr, 0, 255);sg = clamp(sg, 0, 255);sb = clamp(sb, 0, 255);borderColor = Color.rgb(sr, sg, sb);//重绘if (Looper.myLooper() == Looper.getMainLooper()) {invalidate();} else {postInvalidate();}}

这段方法中。大家能够看到我们分别去计算了spotX、offLineWidth、borderColor这三个实例变量的值
而计算这三个实例变量的值主要是由mapValueFromRangeToRange()方法来完毕,那么mapValueFromRangeToRange()方法是个什么鬼?

    private double mapValueFromRangeToRange(double value, double fromLow,double fromHigh, double toLow, double toHigh) {double fromRangeSize = fromHigh - fromLow;double toRangeSize = toHigh - toLow;double valueScale = (value - fromLow) / fromRangeSize;return toLow + (valueScale * toRangeSize);}

一眼望去,各种加减乘除,不喜欢算法的童鞋头都大了。事实上这些运算是相当的简单,基本的思想就是:以一个范围内的值,去映射另外一个给定的范围。通俗讲就是:给你一个范围[0,1]rangeA。在给你一个用于映射的范围[50,100]rangeB。那么如今给你一个在rangeA中的值 0.3。你去在rangeB中计算出相应比例的值。

这个够简单吧。

result = 50 + ((0.3-0) / (1- 0))*(100 - 50) ;

上面的代码中仅仅是将每一步拆分开来,大家能够合在一起看看是不是与上面的式子相等,ok,这样我们就计算出spotX和offLineWidth的值,所以spotX的值也就是会在spotMinX 到 spotMaxX的范围中递增或者递减(由于属性动画回传过来的值也是递增或者递减)。同理offLineWidth和背景色的计算也是一样。我们搞懂mapValueFromRangeToRange后calculateToggleEffect()方法中的逻辑就非常非常easy了。我就不累赘了~那么到此为止ToggleButton的实现已经带大家所有分析完毕。

二、VoiceSeekBar

直接上代码

package com.zuck.definitionview.views;import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;import com.zuck.definitionview.R;/*** 仿iPhone VoiceSeekBar* * 2015-7-16* * @author zuck**/
@SuppressLint("NewApi")
public class VoiceSeekBar extends View {/*** 手柄半径比率*/private static final float SPOTRADIUSRATE = 13.f/40.f;/*** 横条默认高度*/private static final int BAR_HEIGHT = 2;/*** 默认间隙宽度*/private static final int SPACE = 30;/*** 最高级别[级别从0開始,而且级别的段数则为:MAXLEVEL + 1]*/private static final int MAXLEVEL = 15;/*** 条形棒宽度*/private int barWidth;/*** 手柄的区域*/private Region spotRegion;/*** 绘制条形棒的矩形*/private Rect rect;// 左边条形棒的颜色,右边条形棒的颜色,手柄的颜色private int leftBarColor, rightBarColor, spotColor;// 手指按下的时候x坐标private float pressX;// 条形棒矩形的左上角定点private int barLeft ,barTop;// 左側符号的X坐标,Y坐标private int signX,signY;// 手柄半径private float radius;private Paint paint, spotPaint;private Bitmap signLeft, signRigh;private int currentLevel;private OnSeekBarChangeListener onSeekBarChangeListener;public interface OnSeekBarChangeListener {void onProgressChanged(VoiceSeekBar seekBar, int progress);void onStartTrackingTouch(VoiceSeekBar seekBar);void onStopTrackingTouch(VoiceSeekBar seekBar);}public VoiceSeekBar(Context context) {this(context, null);}public VoiceSeekBar(Context context, AttributeSet attrs) {this(context, attrs, 0);}public VoiceSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// 关闭硬件加速setLayerType(LAYER_TYPE_SOFTWARE, null);  init();}private void init() {//初始化当前级别currentLevel = 0;// 初始化载入左边与右边的两张符号图标signLeft = BitmapFactory.decodeResource(getResources(), R.drawable.minus);signRigh = BitmapFactory.decodeResource(getResources(), R.drawable.plus);// 手柄左边/右边的条形棒颜色leftBarColor = Color.parseColor("#4ebb7f");rightBarColor = Color.parseColor("#dadbda");// 手柄颜色spotColor = rightBarColor;paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);paint.setStyle(Paint.Style.FILL);paint.setStrokeCap(Paint.Cap.ROUND);spotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);spotPaint.setColor(spotColor);spotPaint.setStyle(Paint.Style.FILL);// 为手柄加入阴影效果spotPaint.setShadowLayer(10, 0, 1, Color.DKGRAY);// 手柄所在的区域spotRegion = new Region();rect = new Rect();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {Resources r = Resources.getSystem();//測量高度,高度默觉得35int heigthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 35, r.getDisplayMetrics());heightMeasureSpec = MeasureSpec.makeMeasureSpec(heigthSize, MeasureSpec.EXACTLY);//測量宽度int widthSize = MeasureSpec.getSize(widthMeasureSpec);int minWidth = (int) (signLeft.getWidth() + signRigh.getWidth() + SPACE * 4 +  heigthSize * SPOTRADIUSRATE);widthSize = Math.max(minWidth, widthSize);widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onLayout(boolean changed, int left, int top, int right,int bottom) {super.onLayout(changed, left, top, right, bottom);final int width = getWidth();final int height = getHeight();//手柄的半径radius = height * SPOTRADIUSRATE;//条形棒宽度barWidth = (int) (width - (signLeft.getWidth() + signRigh.getWidth() + SPACE * 4 + radius));//计算条形棒開始绘制的左上角顶点位置barLeft = (int) Math.ceil((width - barWidth) / 2.f);barTop = (int) Math.ceil((height - BAR_HEIGHT) / 2.f);//计算左边符号绘制的位置signX = (int) (barLeft - signLeft.getWidth() - SPACE);signY = (height - signLeft.getHeight()) / 2;calcCurrentPressX();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//0.绘制左右两边的符号canvas.drawBitmap(signLeft, signX, signY, paint);canvas.drawBitmap(signRigh, barWidth + barLeft + SPACE, signY, paint);//1.绘制圆形手柄//计算手柄圆点坐标x值float cx = 0;float cy = barTop + BAR_HEIGHT / 2; if(pressX <= radius + barLeft){//手指触摸的范围在:从最左側到横条開始的一个手柄半径距离范围cx = barLeft + radius;}else if (pressX > barLeft + radius && pressX <= barWidth + barLeft - radius){//手指触摸点在 横条最左側開始加上一个手柄半径距离到横条最右側减去一个手柄半径距离范围之内cx = pressX;} else {//手指触摸点超过横条最右側减去一个手柄半径距离的范围cx = barLeft + barWidth - radius;}canvas.drawCircle(cx, cy, radius, spotPaint);//2.绘制左边的条形棒int leftBarRight = (int) (cx - radius);int barBottom = barTop + BAR_HEIGHT;rect.set(barLeft, barTop, leftBarRight, barBottom);paint.setColor(leftBarColor);canvas.drawRect(rect, paint);//3.绘制右边的条形棒int rightBarLeft = (int) (leftBarRight + radius * 2);rect.set(rightBarLeft, barTop, barWidth + barLeft, barBottom);paint.setColor(rightBarColor);canvas.drawRect(rect, paint);//4.记录当前手柄所在的区域(区域的范围扩大一个半径范围)int regionLeft = (int)(cx - 2 * radius);spotRegion.set(regionLeft, (int) -radius, (int)(radius * 4) + regionLeft, (int)(radius * 4));}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {getParent().requestDisallowInterceptTouchEvent(true);return super.dispatchTouchEvent(ev);}@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouchEvent(MotionEvent event) {int action = event.getAction();switch (action) {case MotionEvent.ACTION_DOWN:pressX = event.getX();float pressY = event.getY();if(!spotRegion.contains((int)pressX, (int)pressY)) {return false;}if(onSeekBarChangeListener != null) onSeekBarChangeListener.onStartTrackingTouch(this);break;case MotionEvent.ACTION_MOVE:pressX = event.getX();int level = CalcCurrentLevel();if(onSeekBarChangeListener != null && currentLevel != level) {currentLevel = level;onSeekBarChangeListener.onProgressChanged(this, level);}invalidate();break;case MotionEvent.ACTION_UP:if(onSeekBarChangeListener != null) onSeekBarChangeListener.onStopTrackingTouch(this);break;}return true;}/*** 设置当前级别* @param currentLevel*/public void setCurrentLevel(int currentLevel) {if(currentLevel < 0){this.currentLevel = 0;} else {this.currentLevel = currentLevel >= MAXLEVEL ?

MAXLEVEL : currentLevel; } } /** * 获取当前级别 * @return */ public int getCurrentLevel() { return currentLevel; } /** * 依据当前级别计算当前pressX值(pressX用来计算手柄所在位置) */ private void calcCurrentPressX() { pressX = barWidth * ((float)currentLevel / MAXLEVEL) + barLeft; } /** * 计算当前的级别 * @return */ private int CalcCurrentLevel() { int level ; if(pressX <= radius + barLeft){//手指触摸的范围在:从最左側到横条開始的一个手柄半径距离范围 level = 0; }else if (pressX > barLeft + radius && pressX <= barWidth + barLeft - radius){//手指触摸点在 横条最左側開始加上一个手柄半径距离到横条最右側减去一个手柄半径距离范围之内 level = (int) (((pressX - barLeft) * MAXLEVEL) / barWidth); } else {//手指触摸点超过横条最右側减去一个手柄半径距离的范围 level = MAXLEVEL; } return level; } public void setOnSeekBarChangeListener(OnSeekBarChangeListener onSeekBarChangeListener) { this.onSeekBarChangeListener = onSeekBarChangeListener; } }

该控件实现逻辑思路与ToggleButton差点儿一致。

我也就不打算讲述了。记录在这里,供以后方便查看。以上代码。能够直接copy粘贴到项目中使用。

改下包名就可以。

代码传送门

转载于:https://www.cnblogs.com/liguangsunls/p/7220968.html

自己定义控件-仿iphone之ToggleButtonamp;VoiceSeekBar相关推荐

  1. 【Android】自己定义控件——仿天猫Indicator

    今天来说说类似天猫的Banner中的小圆点是怎么做的(图中绿圈部分) 在学习自己定义控件之前,我用的是很二的方法,直接在布局中放入多个ImageView,然后代码中依据Pager切换来改变图片.这样的 ...

  2. android 原理 组合控件_Android自定义控件进阶01-自定义控件开发套路与流程

    Android自定义控件进阶01-自定义控件开发套路与流程本章节为什么要叫进阶篇?(虽然讲的是基础内容),因为从本篇开始,将会逐渐揭开自定义View的神秘面纱,每一篇都将比上一篇内容更加深入,利用所学 ...

  3. iOS项目开发实战——使用Xcode6设计自己定义控件与图形

    在iOS开发中,有很多控件都是Xcode默认提供的.使用这些控件是很方便的.可是因为某些须要.须要自己设计控件,那么应该怎么做呢?在Xcode6中提供了这种接口,同意开发人员高速开发自己定义控件,而且 ...

  4. git-osc自己定义控件之:CircleImageView

    git-osc自己定义控件之:CircleImageView 一.CircleImageView的使用 在项目中能够发现,用户的头像都是圆形的.感觉非常好奇,昨天最终发现了,原来是自定了一个Image ...

  5. android抖音自动刷新,Android 使用SwipeRefreshLayout控件仿抖音做的视频下拉刷新效果...

    SwipeRefreshLayout(这个控件),我先跟大家介绍一下这个控件: 一.SwipeRefreshLayout简单介绍 •先看以下官方文档,已有了很详细的描述了. 官方文档说明 •这里我再大 ...

  6. 自己定义控件事实上非常easy1/6

    尊重原创转载请注明:From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige 侵权必究! 炮兵镇楼 上一节我们粗略地讲了下怎样去实现我 ...

  7. Android自己定义控件之应用程序首页轮播图

    如今基本上大多数的Android应用程序的首页都有轮播图.就是像下图这种(此图为转载的一篇博文中的图.拿来直接用了): 像这种组件我相信大多数的应用程序都会使用到,本文就是自己定义一个这种组件,能够动 ...

  8. 安卓自定义日期控件(仿QQ,IOS7)

    还记得上篇:高大上的安卓日期时间选择器,本篇是根据上篇修改而来,先看下qq中日期选择的效果: 鉴于目前还没有相似的开源日期控件,因此本人花费了一些时间修改了下之前的日期控件,效果如图: 虽说相似度不是 ...

  9. Android自己定义控件2-简单的写字板控件

    概述 上一篇文章我们对自己定义控件进行了一个大体的知识介绍. 今天就来学习自己定义一个简单的写字板控件. 先来看看效果图 就是简单的依据手指写下的轨迹去画出内容 实现 在上一篇文章里提到了androi ...

最新文章

  1. spacevim 添加自动折行
  2. Java 基本数据类型
  3. 官网快速搭建spring boot 项目
  4. 无法访问gcr.io的几种解决办法
  5. TeXworks 0.4.5 发布,TeX 编辑器
  6. [Java基础]List集合
  7. 轮询数据库 java_谁做过定时任务,轮询查询数据。
  8. python (高级消息队列)普通、进程、进程池的消息队列
  9. 大数据之-Hadoop完全分布式_集群时间同步---大数据之hadoop工作笔记0043
  10. [转载]Yahoo!的分布式数据平台PNUTS简介及感悟
  11. Linux学习笔记——CentOS
  12. Android 微信授权登陆
  13. 微信小程序--七彩爱心加载动画
  14. Adobe带你解锁办会新技能
  15. java计算机毕业设计O2O生鲜果蔬电商设计与实现源码+数据库+系统+lw文档
  16. win7+Linux(centos 7.2)双系统--总结
  17. 《比尔总动员》今日敞开测验 激活码炽热发放
  18. java读取微信证书_Java中的微信支付(2):API V3 微信平台证书的获取与刷新
  19. PAT 乙级 1075  链表元素分类
  20. DD-WRT封杀P2P

热门文章

  1. 大学生学java要去培训机构吗?
  2. 0基础入门前端,会遇到哪些困难?
  3. oracle排序后第一条,Oracle排序取第一条数据
  4. wireshark 开始抓包
  5. 中断程序_ABB机器人中断程序详解(安川FANUC)
  6. STM8学习笔记---点亮LED灯
  7. NYOJ-喷水装置(一)(贪心)
  8. 反卷积(Deconvolution)、上采样(UNSampling)与上池化(UnPooling)加入自己的思考(pytorch函数)(二)
  9. Unsupported major.minor version 52.0解决
  10. 转:全卷积网络(FCN)与图像分割