自定义渐变环形进度条+自定义seekbar

先看大致效果

自定义渐变环形进度条
大致展示的 点。刻度,圆角,弧线,字等简单的元素
这个仅仅是个展示效果,内容比较杂,自己用的话可能要屏蔽一些效果,提供的主要思路。
有什么问题可以及时联系我。

最新添加 android自定义seekbar 圆形thumb
android自定义seekbar圆形thumb
这个设计用seek改很麻烦,因为起始和实际有偏差,加padding也不好使圆形thumb的中心 手的位置,但seek默认进度是蓝色右边,实际上改出来就会这样,想了下不如直接三个view一拼
在这里插入图片描述

android自定义seekbar圆形

android自定义seekbar圆形
我选择直接直接用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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值