转载请标明出处:
http://blog.csdn.net/liuzg1220;
本文出自:【HugeBug的博客】
简介
seekBar大家都不陌生吧,圆形的seekbar不知道你有没有见过,见过的你又没有写过呢?
几年的一个项目,老板对我说android 自带的seekbar太丑了,让我写一个圆的,我想了想觉得这个难不倒我,于是就试着写了一个。先看效果图:
分析
首先这是一个自定的view,这是毋庸置疑的。这个自定义的view怎么写呢?根据上图的观察,我们看到外面的一个圆环,圆环里面有一个用来显示进度的textView。OK,带着这样的思路我们思考一下如何实现。
第一件事我能想到的是画两个同心圆,两个圆的半径不一样。产生一个圆环。
第二件要做的事我们要根据手指滑动位置画不同颜色的圆弧,这样就可以产生进度条的效果了。
第三件事我们要在中间的显示具体的数值,事实上具体的数字这里还有一个圆形的背景图。
分析完了,不扯蛋,直接上代码:
代码实现
自定义的MCircleSeekBar :
<span style="font-size:14px;">package com.lzg.circleseekbar.widget;
/**
* @author lzg
* 2014/4/13
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.lzg.circleseekbar.R;
/**
* The Class CircularSeekBar.
*/
public class MCircleSeekBar extends View {
/** The context */
private Context mContext;
/** seekbar变化监听 */
private OnSeekChangeListener mListener;
/** 画圆环的paint */
private Paint circleColor;
/** 画内圆的paint */
private Paint innerColor;
/** 圆环的Paint,其实是画弧形 */
private Paint circleRing;
/** 移动之后的角度 */
private int angle = 0;
/** 12点位置的开始角度 */
private int startAngle = 270;
/** 圆环宽度 */
private int barWidth = 30;
/** view的宽 */
private int width;
/** view的高 */
private int height;
/** seekbar总值 */
private int maxProgress = 100;
/** 当前的百分值 */
private int progress;
/** 百分值 */
private int progressPercent;
/** 内圆半径 */
private float innerRadius;
/** 外圆半径 */
private float outerRadius;
/** 圆心X坐标 */
private float cx;
/** 圆心Y坐标 */
private float cy;
/** 画圆图层左边边距 */
private float left;
/** 画圆图层右边边距 */
private float right;
/** T画圆图层顶端边距 */
private float top;
/** 画圆图层底边边距 */
private float bottom;
/** 背景图左边边距 */
private float bgLeft;
/** 背景图右边边距 */
private float bgRight;
/** 背景图顶端边距 */
private float bgTop;
/** 背景图底边边距 */
private float bgBottom;
/** progressMark X坐标 */
private float dx;
/** progressMark Y坐标 */
private float dy;
/** 12点钟位置的X坐标 */
private float startPointX;
/** 12点钟位置的Y坐标 */
private float startPointY;
/**
* 标记的seekbar当前位置的X坐标,预设置值为12点位置
*/
private float markPointX;
/**
* T标记的seekbar当前位置的Y坐标,预设置值为12点位置
*/
private float markPointY;
/**
* 进度调整指数
*/
private float adjustmentFactor = 100;
/** 正常状态下的进度条点 */
private Bitmap progressMark;
/** 手指按下后的进度条点 */
private Bitmap progressMarkPressed;
/**
* 中间背景图
*/
private Bitmap progressBg;
/** 是否手指按下的标志位 */
private boolean IS_PRESSED = false;
/**
* 绘制圆中间的背景图的Paint
*/
private Paint paint = null;
private Canvas canvas = new Canvas();
/** 画圆图层. */
private RectF rect = new RectF();
/** 背景图层 */
RectF rectBg = new RectF();
{
mListener = new OnSeekChangeListener() {
@Override
public void onProgressChange(MCircleSeekBar view, int newProgress) {
}
};
/*
* Paint.ANTI_ALIAS_FLAG为抗锯齿
*/
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
circleColor = new Paint();
innerColor = new Paint();
circleRing = new Paint();
/*
* 设置外圆的颜色为蓝色
*/
circleColor.setColor(Color.GRAY);// Set default background color to Gray
/*
* 设置内圆的颜色
*/
innerColor.setColor(Color.parseColor("#313131"));
/*
* 设置圆环的颜色
*/
circleRing.setColor(Color.parseColor("#ff33b5e5"));
/*
* Paint.ANTI_ALIAS_FLAG为抗锯齿
*/
circleColor.setAntiAlias(true);
innerColor.setAntiAlias(true);
circleRing.setAntiAlias(true);
circleColor.setStrokeWidth(5);
innerColor.setStrokeWidth(5);
circleRing.setStrokeWidth(5);
circleRing.setStyle(Paint.Style.FILL);
}
/**
* MCircleSeekBar的构造方法.
*
* @param context
* @param attrs
* @param defStyle
*/
public MCircleSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
initDrawable();
}
/**
* MCircleSeekBar的构造方法.
*
* @param context
* @param attrs
*/
public MCircleSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
initDrawable();
}
/**
* MCircleSeekBar的构造方法.
*
* @param context
*/
public MCircleSeekBar(Context context) {
super(context);
mContext = context;
initDrawable();
}
/**
* 初始化图片
*/
public void initDrawable() {
progressMark = BitmapFactory.decodeResource(mContext.getResources(),
R.drawable.seek_bar);
progressMarkPressed = BitmapFactory.decodeResource(
mContext.getResources(), R.drawable.seek_bar1);
progressBg = BitmapFactory.decodeResource(mContext.getResources(),
R.drawable.open_perecent_bg);
}
/*
* 重写view的计算方法
*
* @see android.view.View#onMeasure(int, int)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getWidth(); // Get View Width
height = getHeight();// Get View Height
int size = (width > height) ? height : width; // Choose the smaller
cx = width / 2; // Center X for circle
cy = height / 2; // Center Y for circle
outerRadius = size / 2 * 80 / 100; // Radius of the outer circle
innerRadius = outerRadius - barWidth; // Radius of the inner circle
left = cx - outerRadius; // Calculate left bound of our rect
right = cx + outerRadius;// Calculate right bound of our rect
top = cy - outerRadius;// Calculate top bound of our rect
bottom = cy + outerRadius;// Calculate bottom bound of our rect
bgLeft = cx - innerRadius;
bgRight = cx + innerRadius;
bgTop = cy - innerRadius;
bgBottom = cy + innerRadius;
startPointX = getXByProgress(progress);
startPointY = getYByProgress(progress);
markPointX = startPointX;// Initial locatino of the marker X coordinate
markPointY = startPointY;// Initial locatino of the marker Y coordinate
rect.set(left, top, right, bottom); // assign size to rect
rectBg.set(bgLeft, bgTop, bgRight, bgBottom);
}
/**
*
*/
@Override
protected void onDraw(Canvas canvas) {
/*
* 画外圆
*/
canvas.drawCircle(cx, cy, outerRadius, circleColor);
/*
* 画圆弧
*/
canvas.drawArc(rect, startAngle, angle, true, circleRing);
/*
* 画内圆
*/
canvas.drawCircle(cx, cy, innerRadius, innerColor);
/*
* 画seek点
*/
canvas.drawBitmap(progressBg, null, rectBg, paint);
/*
* 获取seek点X坐标
*/
dx = getXFromAngle();
/*
* 获取seek点Y坐标
*/
dy = getYFromAngle();
/*
* 绘制seek点的位置
*/
drawMarkerAtProgress(canvas);
super.onDraw(canvas);
}
/**
* 画progressMark(seek)在圆环上的位置.
*
* @param canvas
*/
public void drawMarkerAtProgress(Canvas canvas) {
if (IS_PRESSED) {
canvas.drawBitmap(progressMarkPressed, dx, dy, null);
} else {
canvas.drawBitmap(progressMark, dx, dy, null);
}
}
/**
* 获取seek点X坐标
*
* @return
*/
public float getXFromAngle() {
int size1 = progressMark.getWidth();
int size2 = progressMarkPressed.getWidth();
int adjust = (size1 > size2) ? size1 : size2;
float x = markPointX - (adjust / 2);
return x;
}
/**
* 获取seek点Y坐标
*
* @return
*/
public float getYFromAngle() {
int size1 = progressMark.getHeight();
int size2 = progressMarkPressed.getHeight();
int adjust = (size1 > size2) ? size1 : size2;
float y = markPointY - (adjust / 2);
return y;
}
/**
* 获取圆弧角度
*
* @return the angle
*/
public int getAngle() {
return angle;
}
/**
* 设置圆弧的角度
*
* @param angle
*/
public void setAngle(int angle) {
this.angle = angle;
float donePercent = (((float) this.angle) / 360) * 100;
float progress = (donePercent / 100) * getMaxProgress();
setProgressPercent(Math.round(donePercent));
setProgress(Math.round(progress));
}
/**
* 设置seekbar变化的监听
*
* @param listener
*/
public void setSeekBarChangeListener(OnSeekChangeListener listener) {
mListener = listener;
}
/**
* 获取seekbar变化的监听
*
* @return the seek bar change listener
*/
public OnSeekChangeListener getSeekBarChangeListener() {
return mListener;
}
/**
* 获得bar宽度
*
* @return the bar width
*/
public int getBarWidth() {
return barWidth;
}
/**
* 设置bar宽度
*
* @param barWidth
*/
public void setBarWidth(int barWidth) {
this.barWidth = barWidth;
}
/**
* 定义seekbar变化监听接口
*
* @see OnSeekChangeEvent
*/
public interface OnSeekChangeListener {
/**
* On progress change.
*
* @param view
* @param newProgress
*/
public void onProgressChange(MCircleSeekBar view, int newProgress);
}
/**
* Gets the max progress.
*
* @return the max progress
*/
public int getMaxProgress() {
return maxProgress;
}
/**
*
* @param maxProgress
*
*/
public void setMaxProgress(int maxProgress) {
this.maxProgress = maxProgress;
}
/**
*
* @return the progress
*/
public int getProgress() {
return progress;
}
/**
*
* @param progress
*
*/
public void setProgress(int progress) {
this.progress = progress;
if (!IS_PRESSED) {
int newPercent = (this.progress * 100) / this.maxProgress;
int newAngle = (newPercent * 360) / 100;
this.setAngle(newAngle);
this.setProgressPercent(newPercent);
}
mListener.onProgressChange(this, this.getProgress());
}
/**
*
* @return
*/
public int getProgressPercent() {
return progressPercent;
}
/**
*
* @param progressPercent
*/
public void setProgressPercent(int progressPercent) {
this.progressPercent = progressPercent;
}
/**
*
* @param color
*/
public void setRingBackgroundColor(int color) {
circleRing.setColor(color);
}
/**
*
* @param color
*/
public void setBackGroundColor(int color) {
innerColor.setColor(color);
}
/**
*
* @param color
*/
public void setProgressColor(int color) {
circleRing.setColor(color);
}
/**
* 重写onTouch方法
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
boolean up = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
moved(x, y, up);
break;
case MotionEvent.ACTION_MOVE:
moved(x, y, up);
break;
case MotionEvent.ACTION_UP:
up = true;
moved(x, y, up);
break;
}
return true;
}
/**
* seek点移动方法
*
* @param x
* the x
* @param y
* the y
* @param up
* the up
*/
private void moved(float x, float y, boolean up) {
float distance = (float) Math.sqrt(Math.pow((x - cx), 2)
+ Math.pow((y - cy), 2));
if (distance < outerRadius + adjustmentFactor
&& distance > innerRadius - adjustmentFactor && !up) {
IS_PRESSED = true;
/*
* 根据三角函数整切定理计算得到X.Y的坐标
*/
markPointX = (float) (cx + outerRadius
* Math.cos(Math.atan2(x - cx, cy - y) - (Math.PI / 2)));
markPointY = (float) (cy + outerRadius
* Math.sin(Math.atan2(x - cx, cy - y) - (Math.PI / 2)));
float degrees = (float) ((float) ((Math.toDegrees(Math.atan2(
x - cx, cy - y)) + 360.0)) % 360.0);
if (degrees < 0) {
degrees += 2 * Math.PI;
}
setAngle(Math.round(degrees));
invalidate();
} else {
IS_PRESSED = false;
invalidate();
}
}
public float getXByProgress(int progress) {
float x = 0;
float angle = (float) (2 * progress * Math.PI / 100);
x = (float) (cx + outerRadius * Math.cos(angle - Math.PI / 2));
return x;
}
public float getYByProgress(int progress) {
float y = 0;
float angle = (float) (2 * progress * Math.PI / 100);
y = (float) (cy + outerRadius * Math.sin(angle - Math.PI / 2));
return y;
}
public void setMarkPointXY(int progress) {
this.progress = progress;
}
/**
*
* @return
*/
public float getAdjustmentFactor() {
return adjustmentFactor;
}
/**
*
* @param adjustmentFactor
*/
public void setAdjustmentFactor(float adjustmentFactor) {
this.adjustmentFactor = adjustmentFactor;
}
}</span>
布局文件activity_main.xml:
<span style="font-size:14px;"><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center_horizontal"
android:background="#e0313131"
tools:context=".MainActivity" >
<com.lzg.circleseekbar.widget.MCircleSeekBar
android:id="@+id/m_circleSeekBar_set_perencet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="horizontal" >
<TextView
android:id="@+id/tv_perencet_set_perencet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textColor="#ffffff"
android:textSize="60sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="%"
android:textColor="#ffffff"
android:textSize="40sp" />
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical" >
<TextView style="@style/line_text_style" />
</LinearLayout>
</FrameLayout></span>
BaseActivity:
<span style="font-size:14px;">package com.lzg.circleseekbar;
import android.app.Activity;
import android.view.View;
/**
*
* @author lzg
*
*/
public class BaseActivity extends Activity {
/**
* 解决强制类型转换的问题
*
* @param id
* @return
*/
@SuppressWarnings("unchecked")
public <T extends View> T getViewById(int id) {
return (T) findViewById(id);
}
}</span>
界面调用的主MainAcitivity:
<span style="font-size:14px;">package com.lzg.circleseekbar;
import android.os.Bundle;
import android.widget.TextView;
import com.lzg.circleseekbar.widget.MCircleSeekBar;
import com.lzg.circleseekbar.widget.MCircleSeekBar.OnSeekChangeListener;
/**
*
* @author lzg
*
*/
public class MainActivity extends BaseActivity {
private TextView tvPerencetValue;
private MCircleSeekBar mCircleSeekBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvPerencetValue = getViewById(R.id.tv_perencet_set_perencet);
mCircleSeekBar = getViewById(R.id.m_circleSeekBar_set_perencet);
mCircleSeekBar.setSeekBarChangeListener(new OnSeekChangeListener() {
public void onProgressChange(MCircleSeekBar view, int newProgress) {
tvPerencetValue.setText(Integer.toString(view.getProgress()));
}
});
}
}</span>
到此代码全部结束了!
是不是感觉代码有点多,思路清晰了,也就水到渠成了。。。
如果你觉得有用就关注我吧,在这里将不定期的发表原创文章!如果你觉得有用就留个言,点个赞吧!
下面是我微信公众号:如果你喜欢就扫扫看吧!
源码下载