Android 自定义垂直,旋转,圆形进度提示 自定义Seekbar

简介: SeekBar是用来调节参数值的,系统默认的一般都不太适合我们,需要我们自定义,里面也有一个RxJava背压的具体应用废话不多说,看图 package com.

SeekBar是用来调节参数值的,系统默认的一般都不太适合我们,需要我们自定义,

里面也有一个RxJava背压的具体应用

废话不多说,看图

 

package com.example.helang.seekbar;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import java.util.concurrent.TimeUnit;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;

/**
 * 自定义拖动seekBar
 */
public class CustomSeekBar extends View {
    private static final String TAG = "CustomSeekBar";
    private static final int radius = 65;//中间圆形进度条的半径
    private static final int thumbSize = 200;

    private int backgroundLineSize = 10;//背景线的宽度
    private int foregroundLineSize = 18;//进度的宽度

    private int lineSize;//整条背景线的长度

    private float touchY;
    private Bitmap thumbBitmap;

    private Paint paint;
    private Paint circlePaint;//绘制进度条的paint

    private RectF backgroundLineRect = new RectF();//背景矩形
    private RectF foregroundLineRect = new RectF();//进度矩形

    private float currentDegrees = 0;//当前的进度,百分比例,不带百分号

    private OnProgressListener onProgressListener;

    public void setOnProgressListener(OnProgressListener onProgressListener) {
        this.onProgressListener = onProgressListener;
    }

    public CustomSeekBar(Context context) {
        this(context,null);
    }

    public CustomSeekBar(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CustomSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initBitmap();
        initPaint();
    }

    /**
     * init bitmap
     */
    private void initBitmap(){
        thumbBitmap = drawableToBitmap(thumbSize,getResources().getDrawable(R.drawable.circle));
    }

    /**
     * init paint
     */
    private void initPaint() {
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setTextSize(10);

        //初始化圆形进度条的Paint
        circlePaint = new Paint();
        circlePaint.setAntiAlias(true); // 抗锯齿
        circlePaint.setDither(true); // 防抖动
        circlePaint.setStrokeWidth(10);
        circlePaint.setShader(null); // 清除上一次的shader
        circlePaint.setStyle(Paint.Style.STROKE); // 设置绘制的圆为空心
        circlePaint.setShadowLayer(10, 10, 10, Color.RED);
        circlePaint.setColor(Color.BLUE); // 设置圆弧的颜色
        circlePaint.setStrokeCap(Paint.Cap.ROUND); // 把每段圆弧改成圆角的
    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawLine(canvas);
        drawThumb(canvas);
        drawCircleProgress(canvas);
    }

    /**
     * draw circle
     * @param canvas
     */
    private void drawCircleProgress(Canvas canvas){
        RectF oval = new RectF(thumbSize/2-radius, thumbSize/2-radius,
                thumbSize/2+radius, thumbSize/2+radius); // 圆的外接正方形
        float alphaAngle = currentDegrees * 360.0f / 100 * 1.0f; // 计算每次画圆弧时扫过的角度,这里计算要注意分母要转为float类型,否则alphaAngle永远为0
        canvas.drawArc(oval, -90, alphaAngle, false, circlePaint);
    }

    /**
     * draw thumb
     * @param canvas
     */
    private void drawThumb(Canvas canvas){
        //添加旋转,Matrix是Bitmap旋转的关键,用于bitmap一些补间动画的操作
        canvas.translate((getWidth()-thumbSize)/2,(100-currentDegrees)/100*lineSize);
        Matrix matrix  = new Matrix();
        matrix.setRotate(currentDegrees*10,thumbSize/2,thumbSize/2);
        canvas.drawBitmap(thumbBitmap,matrix,null);//旋转背景
    }

    /**
     * draw lines
     * @param canvas
     */
    private void drawLine(Canvas canvas){
        //绘制背景线
        backgroundLineRect.set((getWidth()-backgroundLineSize)/2,thumbSize/2,
                (getWidth()+backgroundLineSize)/2, getParentHeight()-thumbSize/2);
        lineSize = getParentHeight() - thumbSize;//去掉被thumb挡住的一部分长度
        paint.setColor(Color.rgb(61,82,89));
        canvas.drawRoundRect(backgroundLineRect, backgroundLineSize/2, backgroundLineSize/2, paint);


        //绘制进度线
        paint.setColor(Color.rgb(90,189,220));//进度线的颜色
        foregroundLineRect.set((getWidth()-foregroundLineSize)/2,
                (getParentHeight()-thumbSize)*(100-currentDegrees)/100+thumbSize/2,
                (getWidth()+foregroundLineSize)/2,getParentHeight()-thumbSize/2);
        canvas.drawRoundRect(foregroundLineRect,foregroundLineSize/2,foregroundLineSize/2,paint);
    }



    /**
     * get ParentHeight
     * @return
     */
    private int getParentHeight(){
        return getHeight();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled())
            return true;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchY = event.getRawY();//记录开始的Y值
                break;
            case MotionEvent.ACTION_MOVE:
                currentDegrees += (touchY-event.getRawY())*1f/(getParentHeight())*100.0f;//当前进度值(100为满)
                if (currentDegrees > 100){//超出去的不计算,默认为100
                    currentDegrees = 100;
                }
                if (currentDegrees<0){//超出去的不计算,默认为0
                    currentDegrees = 0;
                }

                if (observableEmitter != null){//使用背压发送
                    observableEmitter.onNext(1);
                }else {//直接发送
                    sendProgress();
                }

                touchY = event.getRawY();
                invalidate();
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        return true;
    }



    private ObservableEmitter<Integer> observableEmitter;


    /**
     *增加背压,防止发射拖动的事件过快,导致内存溢出
     */
    public void addBackPressure(){
        Observable.create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                observableEmitter = emitter;
            }
        }).sample(500, TimeUnit.MILLISECONDS)
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) throws Exception {
                sendProgress();
            }
        });


    }

    /**
     * 发送进度
     */
    private void sendProgress(){
        if (onProgressListener != null){
            onProgressListener.onProgress(currentDegrees);
        }
    }

    /**
     * 设置当前进度
     * @param currentDegrees
     */
    public void setCurrentDegrees(float currentDegrees){
        this.currentDegrees = currentDegrees;
        invalidate();
    }


    public interface OnProgressListener{
        void onProgress(float progress);
    }

    /**
     *  make a drawable to a bitmap
     * @param drawable drawable you want convert
     * @return converted bitmap
     */
    private Bitmap drawableToBitmap(int size, Drawable drawable) {
        Bitmap bitmap = null;
        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            bitmap = bitmapDrawable.getBitmap();
            if (bitmap != null && bitmap.getHeight() > 0) {
                Matrix matrix = new Matrix();
                float scaleHeight = size * 1.0f / bitmapDrawable.getIntrinsicHeight();
                matrix.postScale(scaleHeight, scaleHeight);
                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
                return bitmap;
            }
        }
        bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bitmap;
    }

}

 

Demo地址:https://github.com/helang1991/SeekBar

 

喜欢的就加个Star,谢谢

目录
相关文章
|
7月前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
549 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
7月前
|
Android开发
Android自定义view之利用PathEffect实现动态效果
本文介绍如何在Android自定义View中利用`PathEffect`实现动态效果。通过改变偏移量,结合`PathEffect`的子类(如`CornerPathEffect`、`DashPathEffect`、`PathDashPathEffect`等)实现路径绘制的动态变化。文章详细解析了各子类的功能与参数,并通过案例代码展示了如何使用`ComposePathEffect`组合效果,以及通过修改偏移量实现动画。最终效果为一个菱形图案沿路径运动,源码附于文末供参考。
140 0
|
7月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
372 65
Android自定义view之网易云推荐歌单界面
|
7月前
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
705 84
|
7月前
|
前端开发 Android开发 UED
讲讲Android为自定义view提供的SurfaceView
本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。
220 3
|
7月前
|
Android开发 开发者
Android自定义view之围棋动画(化繁为简)
本文介绍了Android自定义View的动画实现,通过两个案例拓展动态效果。第一个案例基于`drawArc`方法实现单次动画,借助布尔值控制动画流程。第二个案例以围棋动画为例,从简单的小球直线运动到双向变速运动,最终实现循环动画效果。代码结构清晰,逻辑简明,展示了如何化繁为简实现复杂动画,帮助读者拓展动态效果设计思路。文末提供完整源码,适合初学者和进阶开发者学习参考。
144 0
Android自定义view之围棋动画(化繁为简)
|
7月前
|
Java Android开发 开发者
Android自定义view之围棋动画
本文详细介绍了在Android中自定义View实现围棋动画的过程。从测量宽高、绘制棋盘背景,到创建固定棋子及动态棋子,最后通过属性动画实现棋子的移动效果。文章还讲解了如何通过自定义属性调整棋子和棋盘的颜色及动画时长,并优化视觉效果,如添加渐变色让白子更明显。最终效果既可作为围棋动画展示,也可用作加载等待动画。代码完整,适合进阶开发者学习参考。
164 0
|
Android开发
Android Studio 自定义设置注释模板
Android Studio 自定义设置注释模板
841 0
Android Studio 自定义设置注释模板
|
2月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
318 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
2月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
290 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡