先看大致效果
大致展示的 点。刻度,圆角,弧线,字等简单的元素
这个仅仅是个展示效果,内容比较杂,自己用的话可能要屏蔽一些效果,提供的主要思路。
有什么问题可以及时联系我。
最新添加 android自定义seekbar 圆形thumb
这个设计用seek改很麻烦,因为起始和实际有偏差,加padding也不好使圆形thumb的中心 手的位置,但seek默认进度是蓝色右边,实际上改出来就会这样,想了下不如直接三个view一拼
我选择直接直接用view拼
class ProgressView : LinearLayout {
constructor(context: Context?) : super(context) {
initView()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
initView()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
initView()
}
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
initView()
}
var mX = -1
var minX = 0
var maxX = 0
var mProgress = 30
var mOnCustomProgressChangeListener: OnCustomProgressChangeListener? = null
var pbBackground: View? = null
var pbProgress: View? = null
var ivThumb: View? = null
val dp7 = dp2px(context, 7f).toInt()
@SuppressLint("ClickableViewAccessibility")
private fun initView() {
inflate(context, R.layout.seekbar_custom_ui, this)
pbBackground = findViewById(R.id.pbBackground)
pbProgress = findViewById(R.id.pbProgress)
ivThumb = findViewById(R.id.ivThumb)
ivThumb?.post {
// 初始化参数
minX = dp7 + ivThumb!!.width / 2
maxX = pbBackground!!.width - minX
log("ProgressView min:$minX")
log("ProgressView max:$maxX")
// 初始化
setProgress(mProgress)
}
pbBackground?.setOnTouchListener { v: View?, event: MotionEvent ->
mX = event.x.toInt()
log("ProgressView r:$mX")
updateX()
mOnCustomProgressChangeListener?.onProgressChange(mProgress)
true
}
}
private fun updateX() {
if (mX < minX) {
mX = minX
} else if (mX > maxX) {
mX = maxX
}
// 拖动圆中心和手对齐
val trueX = (mX - ivThumb!!.width / 2).toFloat()
ivThumb!!.x = trueX
// 进度和手半圆加间距对齐
val layoutParams = pbProgress!!.layoutParams
layoutParams.width = (trueX + ivThumb!!.width + dp7).toInt()
pbProgress!!.setLayoutParams(layoutParams)
// 根据比例算出进度
mProgress = (mX - minX) * 100 / (maxX - minX)
log("ProgressView progress:$mProgress")
}
fun setProgress(progress: Int) {
// 根据进度算出比例
mX = progress * (maxX - minX) / 100 + minX
updateX()
}
fun setOnProgressChangeListener(onProgressChangeListener: OnCustomProgressChangeListener?) {
this.mOnCustomProgressChangeListener = onProgressChangeListener
}
}
public interface OnCustomProgressChangeListener {
fun onProgressChange(progress: Int)
}
看下面的xml非常直观 简单实现
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp">
<!-- ui设计不一样 所以选择自定义组合view-->
<View
android:id="@+id/pbBackground"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@drawable/bg_ps_background" />
<View
android:id="@+id/pbProgress"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@drawable/bg_ps_progress_bar" />
<ImageView
android:id="@+id/ivThumb"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center_vertical"
android:layout_margin="7dp"
android:src="@drawable/thumb_seek_bar" />
</FrameLayout>
3个draw是shape画的常规图形
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="30dp" />
<solid android:color="#FF1859E9" />
</shape>
module源码下载
核心代码
package com.justforview.view;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.SeekBar;
import com.justforview.R;
import java.text.DecimalFormat;
/**
* Created by Rex on 2017/3/16.
* 核心LinearGradient
*/
public class GradualChangePbView extends View {
// private Paint mPaint;
private int center;
private int radius;
private int roundWidth = 30;
private float lastAngle = 180;//最左边作为起点
private float startAngle = 180;//最左边作为起点
private int currI;
private int max = 30;
private long needTime = 1000;
private Paint mPaint;
private String str = "哦,你们要什么渐变进度条?Gradual";
private float bili;
private int maxProgress = 100;
private int currProgress = 65;//测试
private Bitmap dstbmp;
private SeekBar seekBar;
private float mRotation;
private boolean seekBarNeedListener;
public GradualChangePbView(Context context) {
this(context, null);
}
public GradualChangePbView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GradualChangePbView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE); //设置空心
mPaint.setColor(Color.parseColor("#eeeeee")); //设置圆环的颜色
mPaint.setStrokeWidth(roundWidth); //设置圆环的宽度
mPaint.setAntiAlias(true); //消除锯齿
}
public void bindSeekbar(SeekBar seekBar) {
this.seekBar = seekBar;
seekBar.setMax(maxProgress);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
setProgress(seekBar.getProgress());
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
@Override
protected void onDraw(Canvas canvas) {
// mCanvas = canvas;
// 可用全局变量canvas 也可用全局数据,在上一步基础上继续绘制。 本次使用的全局数据(逻辑更简单)。因为canvas每次都是新的 性能差不多其实,但前者肯定更好
super.onDraw(canvas);
// 画最外层大圆
center = getWidth() / 2;
radius = (center - roundWidth / 2);
//全部绘制为max份 currI份已经绘制
//也可以用progress取代
bili = currI * 1.0f / max;
// mCanvas.save();
// mCanvas.translate(center, center);
// mCanvas.rotate(360 * bili);
// mCanvas.restore();
currI++;
if (currI <= max) {
drawBgPb(canvas);
paintArc(currProgress, canvas);
painTextPathPb(canvas);
painDrawTextPb(canvas);
paintPointPath(currProgress, canvas);
drawTickmark(currProgress, canvas);
postInvalidateDelayed(needTime / max);
} else {
bili = 1;
drawBgPb(canvas);
paintArc(currProgress, canvas);
painTextPathPb(canvas);
painDrawTextPb(canvas);
paintPointPath(currProgress, canvas);
drawTickmark(currProgress, canvas);
}
}
/**
* 绘制灰色总圆环
*/
private void drawBgPb(Canvas canvas) {
Paint p = new Paint();
p.setStrokeWidth(roundWidth); //设置圆环的宽度
p.setStyle(Paint.Style.STROKE);
p.setColor(Color.WHITE);
canvas.drawCircle(center, center, radius, mPaint); //画出圆环
}
/**
* 绘制刻度 即间隔
*/
private void drawTickmark(int progress, Canvas canvas) {
Paint p = new Paint();
p.setStrokeWidth(roundWidth); //设置圆环的宽度
p.setStyle(Paint.Style.FILL_AND_STROKE);
//横线刻度
RectF oval = new RectF(center - radius + roundWidth * 6, center - radius + roundWidth * 6, center
+ radius - roundWidth * 6, center + radius - roundWidth * 6); //用于定义的圆弧的形状和大小的界限
setPaintShaderColor(p, false);//刻度不能开圆角会导致扩散
for (int i = 0; i <= 360 * progress * bili / maxProgress; i += 20) {
canvas.drawArc(oval, startAngle + i, 2f, false, p);
}
//点刻度
RectF oval2 = new RectF(center - radius + roundWidth, center - radius + roundWidth, center
+ radius - roundWidth, center + radius - roundWidth); //用于定义的圆弧的形状和大小的界限
//可以画点 此处直接利用圆角变为点刻度
p.setStrokeWidth(5); //设置圆环的宽度
setPaintShaderColor(p, true);//刻度不能开圆角会导致扩散
for (int i = 0; i <= 360 * progress * bili / maxProgress; i += 20) {
canvas.drawArc(oval2, startAngle + i, 1f, false, p);
}
}
/**
* @param progress 画进度弧线
*/
private void paintArc(int progress, Canvas canvas) {
//progress%
float range = 360 * progress * 1.0f / maxProgress;
// 画圆弧 ,画圆环的进度
//设置进度是实心还是空心
Paint p = new Paint();
p.setStrokeWidth(roundWidth); //设置圆环的宽度
RectF oval = new RectF(center - radius, center - radius, center
+ radius, center + radius); //用于定义的圆弧的形状和大小的界限
p.setStyle(Paint.Style.STROKE);
setPaintShaderColor(p, isCapROUND);
canvas.drawArc(oval, startAngle, range * bili, false, p); //根据进度画圆弧
//1为起点 2为长度
}
/**
* 绘制进度文字
*
* @param canvas
*/
private void painDrawTextPb(Canvas canvas) {
Paint paint = new Paint();
adjustPhotoRotation(canvas, paint);
DecimalFormat fnum = new DecimalFormat("##0.00");
String alProgress = fnum.format(currProgress * bili);
alProgress = alProgress + "%";
paint.setStrokeWidth(0);
paint.setColor(Color.BLUE);
paint.setTextSize(18);
paint.setTypeface(Typeface.DEFAULT_BOLD); //设置字体
float textWidth = paint.measureText(alProgress); //测量字体宽度,我们需要根据字体的宽度设置在圆环中间
canvas.drawText(alProgress, center - textWidth / 2, center + 18 / 2, paint);
}
/**
* 获取旋转之后的点
*
* @param rotation
* @return
*/
private PointF getPointByRotation(float rotation) {
return null;
}
private void adjustPhotoRotation(Canvas canvas, Paint p) {
//中间可以操作的按钮
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.mipmap.arr_rotate);
// 定义矩阵对象
Matrix matrix = new Matrix();
// 缩放原图
matrix.postScale(1f, 1f);
// 向左旋转45度,参数为正则向右旋转
matrix.postRotate(currProgress * bili * 32);
dstbmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(),
matrix, true);
// 在画布上绘制旋转后的位图
//放在坐标为0,200的位置
canvas.drawBitmap(dstbmp, center - dstbmp.getWidth() * 1.0f / 2, center - dstbmp.getHeight() * 1.0f / 2, p);
// float hw = dstbmp.getWidth() * 1.0f / 2;
// float hh = dstbmp.getHeight() * 1.0f / 2;
//
// float mRadius = (float) Math.sqrt((hw * hw + hh * hh));
// float mCenterRotation = (float) Math.toDegrees(Math.atan(hh / hw));
//
// PointF pointF = new PointF();
// double rot = currProgress * bili * 32 * Math.PI / 180;
// pointF.x = c + (float) (mRadius * Math.cos(rot));
// pointF.y = mMateBase.getmY() + (float) (mRadius * Math.sin(rot));
}
private float mDownX;
private float mDownY;
@Override
public boolean onTouchEvent(MotionEvent event) {
// Matrix.invert(Matrix inverse)计算bitmap旋转移动之前的坐标
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getRawX();
mDownY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getRawX();
float moveY = event.getRawY();
float angle = getDegress(moveX, moveY) - getDegress(mDownX, mDownY);
int pb = (int) ((currProgress + angle) % 360);
Log.d("rex", "currProgress -> " + currProgress);
Log.d("rex", "degress -> " + pb);
if (seekBar != null) {
seekBar.setProgress(pb);
}else {
setProgress(pb);
}
mDownX = moveX;
mDownY = moveY;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
/**
* 绘制环绕文字
*
* @param canvas
*/
private void painTextPathPb(final Canvas canvas) {
Paint p = new Paint();
final RectF oval = new RectF(center - radius + roundWidth * 4, center - radius + roundWidth * 4, center
+ radius - roundWidth * 4, center + radius - roundWidth * 4); //用于定义的圆弧的形状和大小的界限
p.setStyle(Paint.Style.FILL);//设置画笔的填充方式
p.setTextSize(30);
setPaintShaderColor(p, false);
lastAngle = 180;//最左边为起点
char[] chars = str.toCharArray();
for (int i = 0; i < currI; i++) {
if (i >= chars.length) {
break;
}
Path path = new Path();
float newAngle = (lastAngle + 360.0f / max) % 360;
if (newAngle == 0) {
newAngle = 360.0f - 0.1f;// 系统不能绘制xx-360
}
path.addArc(oval, lastAngle, newAngle * bili);
canvas.drawTextOnPath(chars[i % chars.length] + "", path, 0, 0, p);
lastAngle = newAngle;
}
//绘制圆形路径
// Path pathCircle=new Path();
// pathCircle.addCircle(0, 0, radius, Path.Direction.CCW);//添加逆时针的圆形路径
// //绘制折线路径
// canvas.drawPath(pathCircle, mPaint);
// Path myPath=new Path();
// myPath.moveTo(150,maxProgress);
// myPath.lineTo(200,45);
// myPath.lineTo(250,maxProgress);
// myPath.lineTo(300,80);
// canvas.drawPath(myPath, paint);
// //绘制三角形路径
// Path pathTr=new Path();
// pathTr.moveTo(350,80);
// pathTr.lineTo(400,30);
// pathTr.lineTo(450,80);
// pathTr.close();
// canvas.drawPath(pathTr, paint);
}
private float getDegress(float newX, float newY) {
// int[] location = new int[2];
// centerPoint.getLocationOnScreen(location);//获取在整个屏幕内的绝对坐标
// float currCX = location[0];
// float currCY = location[1];
float x = newX - center;
float y = newY - center;
return (float) Math.toDegrees(Math.atan2(y, x));
}
/**
* @param progress 进度确定弧度范围
*/
private void paintPointPath(int progress, Canvas canvas) {
//progress%
float range = 360 * progress * 1.0f / maxProgress;
Paint p = new Paint();
final RectF oval = new RectF(center - radius + roundWidth * 2, center - radius + roundWidth * 2, center
+ radius - roundWidth * 2, center + radius - roundWidth * 2); //用于定义的圆弧的形状和大小的界限
p.setStyle(Paint.Style.FILL);//设置画笔的填充方式
setPaintShaderColor(p, isCapROUND);
float singlPoint = 10f;
float lineWidth = 0.3f;
int index = (int) (progress * bili);
float start = 180f;
for (int i = 0; i < index; i++) {
// 绘制间隔快
canvas.drawArc(oval, start + singlPoint - lineWidth, lineWidth, false, p);
start = (start + singlPoint);
}
}
/**
* 是否开启圆角
*
* @param p
* @param isCap
*/
private void setPaintShaderColor(Paint p, boolean isCap) {
//直线渐变
// LinearGradient shader = new LinearGradient(
// 0, 0,
// x2, y2,
// colors,
// positions,
// Shader.TileMode.MIRROR);
//圆渐变
SweepGradient sweepGradient = new SweepGradient(center, center, colors, positions);
/**
* 此处要注意 如果你要让进度以startAngle开始必须旋转 不然是从0度开始渐变
* -roundWidth/2 是因为圆角效果会有多余占位 颜色起点却从直线开始
*/
Matrix matrix = new Matrix();
matrix.setRotate(startAngle - roundWidth / 2, center, center);
sweepGradient.setLocalMatrix(matrix);
p.setShader(sweepGradient);
if (isCap) {
p.setStrokeCap(Paint.Cap.ROUND);//设置为圆角
}
}
/***
* 绘制外部彩色线条和小红圈
* 利用PathMeasure的getTranslate测量出需要绘制的圆弧的末端的坐标位置
*
* @param formDegree 起始角度
* @param toDegree 旋转角度
* @param canvas 画布
* @param bitmap 四种状态的模糊Bitmap
* @param color 四种状态的实心颜色
*/
//默认值
private int[] colors = {Color.parseColor("#3600b2"), Color.parseColor("#ff3600"), Color.parseColor("#ffc000")};
private float[] positions = {0, 0.5f, 1.0f};
private boolean isCapROUND = true;
/**
* 设置渐变色
*
* @param colors
* @param positions
* @param isCapROUND 是否使用圆角
* @return
*/
public void setPaintShaderColorData(int[] colors, float positions[], boolean isCapROUND) {
this.colors = colors;
this.positions = positions;
this.isCapROUND = isCapROUND;
invalidate();
}
/**
* 设置渐变色
*
* @param colorsString
* @param positions
* @param isCapROUND 是否使用圆角
* @return
*/
public void setPaintShaderColorData(String[] colorsString, float positions[], boolean isCapROUND) {
if (colorsString != null) {
int[] colors = new int[colorsString.length];
for (int i = 0; i < colorsString.length; i++) {
colors[i] = Color.parseColor(colorsString[i]);
}
}
setPaintShaderColorData(colors, positions, isCapROUND);
}
public void setMaxProgress(int maxProgress) {
this.maxProgress = maxProgress;
postInvalidate();
}
public void setProgress(final int curr) {
this.currProgress = curr;
postInvalidate();
}
public void setRadius(int radius) {
this.radius = radius;
}
public void setRoundWidth(int roundWidth) {
this.roundWidth = roundWidth;
}
public void setStartAngle(float startAngle) {
this.startAngle = startAngle;
}
public void setNeedTime(long needTime) {
this.needTime = needTime;
}
public int getRadius() {
return radius;
}
public int getRoundWidth() {
return roundWidth;
}
public float getStartAngle() {
return startAngle;
}
public long getNeedTime() {
return needTime;
}
}
XMl和JAVA
GradualChangePbView ccpbView = Fid(R.id.gcpbView);
SeekBar seekBar = Fid(R.id.sb);
ccpbView.bindSeekbar(seekBar);
seekBar.setProgress(50);
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.justforview.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Just for View"
android:textSize="18sp"
/>
<com.justforview.view.GradualChangePbView
android:id="@+id/gcpbView"
android:layout_width="400dp"
android:layout_height="400dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
android:background="#DEDEDE"
/>
<SeekBar
android:id="@+id/sb"
android:layout_width="match_parent" android:layout_height="wrap_content"/>
</LinearLayout>