自己定义控件-仿iphone之ToggleButton&VoiceSeekBar
由于项目中须要使用开关切换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;
// 边框大小 默觉得2px
private 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() {
@Override
public 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() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
calculateToggleEffect(Double.parseDouble(animation.getAnimatedValue().toString()));
}
});
//OvershootInterpolator : 结束时会超过给定数值,可是最后一定返回给定值
animation.setInterpolator(new OvershootInterpolator(1.5f));
animation.setDuration(500);
}
@Override
protected 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);
}
@Override
protected 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() {
@Override
public 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();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Resources r = Resources.getSystem();
//測量高度,高度默觉得35
int 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);
}
@Override
protected 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();
}
@Override
protected 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));
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public 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粘贴到项目中使用。
改下包名就可以。
自己定义控件-仿iphone之ToggleButton&VoiceSeekBar的更多相关文章
- 【Android】自己定义控件——仿天猫Indicator
今天来说说类似天猫的Banner中的小圆点是怎么做的(图中绿圈部分) 在学习自己定义控件之前,我用的是很二的方法,直接在布局中放入多个ImageView,然后代码中依据Pager切换来改变图片.这样的 ...
- 自己定义控件:onDraw 方法实现仿 iOS 的开关效果
概述 本文主要解说怎样在 Android 下实现高仿 iOS 的开关按钮,并不是是在 Android 自带的 ToggleButton 上改动,而是使用 API 提供的 onDraw.onMeasur ...
- Android应用之——自己定义控件ToggleButton
我们经常会看到非常多优秀的app上面都有一些非常美丽的控件,用户体验非常好.比方togglebutton就是一个非常好的样例,IOS系统以下那个精致的togglebutton现在在android以下也 ...
- Android自定义控件1--自定义控件介绍
Android控件基本介绍 Android本身提供了很多控件比如我们常用的有文本控件TextView和EditText:按钮控件Button和ImageButton状态开关按钮ToggleButton ...
- Android自己定义控件系列二:自己定义开关button(一)
这一次我们将会实现一个完整纯粹的自己定义控件,而不是像之前的组合控件一样.拿系统的控件来实现.计划分为三部分:自己定义控件的基本部分,自己定义控件的触摸事件的处理和自己定义控件的自己定义属性: 以下就 ...
- Android 实现形态各异的双向側滑菜单 自己定义控件来袭
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39670935.本文出自:[张鸿洋的博客] 1.概述 关于自己定义控件側滑已经写了 ...
- Android自己定义控件之应用程序首页轮播图
如今基本上大多数的Android应用程序的首页都有轮播图.就是像下图这种(此图为转载的一篇博文中的图.拿来直接用了): 像这种组件我相信大多数的应用程序都会使用到,本文就是自己定义一个这种组件,能够动 ...
- Qt 界面使用自己定义控件 "提升为"
1.效果图 我做了一个很easy的样例,一个能够显示颜色的QLabel,边上有个button,点击,跳出颜色选取的Dialog,然后选择一个颜色.这个QLabel会变成什么颜色. 2.ColorLab ...
- Android自己定义控件(状态提示图表)
[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处.尊重分享成果] 1 背景 前面分析那么多系统源代码了.也该暂停下来歇息一下,趁昨晚闲着看见一个有意思的需求就操 ...
随机推荐
- CF615C Running Track
思路: kmp + 二分. 实现: #include <iostream> #include <cstdio> #include <algorithm> #incl ...
- Python语言之类
1.一个空类 #Filename : emptyclass.py class Empty: pass e = Empty() print( e ) #<__main__.Empty object ...
- Centos7 Openstack - (第二节)添加认证服务(Keystone)
Centos7 install Openstack - (第二节)添加认证服务(Keystone) 我的blog地址:http://www.cnblogs.com/caoguo 根据openstack ...
- (转) Hibernate检索方式概述
http://blog.csdn.net/yerenyuan_pku/article/details/70554816 Hibernate检索方式概述 我们在对数据库的操作中,最常用的是select, ...
- yum进程被占用
使用yum安装软件的时候出现,/var/run/yum.pid 已被锁定,PID 为 6503 的另一个程序正在运行的问题 [root@localhost mysql]# yum install gc ...
- 用C#在Visual Studio写Javascript单元测试
1.在vs创建一个标准的单元测试工程 2.引用nuget包:Edge.js 我是用的是6.11.2版本 3.编写JsRunner类 using EdgeJs; using System; using ...
- SpringMVC参数绑定、Post乱码解决方法
从客户端请求key/value数据,经过参数绑定,将key/value数据绑定到controller方法的形参上. springmvc中,接收页面提交的数据是通过方法形参来接收.而不是在control ...
- Git学习总结(标签管理)
在Git中打标签非常简单,首先,切换到需要打标签的分支上: 然后,敲命令git tag <name>就可以打一个新标签: $ git tag v1. 可以用命令git tag查看所有标签: ...
- 293. [NOI2000] 单词查找树——COGS
293. [NOI2000] 单词查找树 ★★ 输入文件:trie.in 输出文件:trie.out 简单对比时间限制:1 s 内存限制:128 MB 在进行文法分析的时候,通常需要检 ...
- UNIX C 总结
--day01--王建立QQ:2529866769今天的内容:一.计算机的框架什么是操作系统?(汽车)加油系统 油门 用户跟加油子系统交互的窗口.(接口)方向系统 方向盘 用户跟方向系统的交互接口.导 ...