自定义View实现环形SeekBar

由于android系统提供的SeekBar是直线型的。但是有些时候我们需要用到其他形状的SeekBar,那么就需要自定义View来实现。这里只是实现了一个环形的,其他形状的类似。

效果图如下

自定义View的java代码

package com.lee.circleseekbar.view;

import com.lee.circleseekbar.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 *
 * CircleSeekBar
 *
 * @author lee
 *
 */

public class CircleSeekBar extends View {

    private final boolean DEBUG = true;
    private final String TAG = "CircleSeekBar";

    private Context mContext = null;
    private AttributeSet mAttrs = null;

    private Drawable mThumbDrawable = null;
    private int mThumbHeight = 0;
    private int mThumbWidth = 0;
    private int[] mThumbNormal = null;
    private int[] mThumbPressed = null;

    private int mSeekBarMax = 0;
    private Paint mSeekBarBackgroundPaint = null;
    private Paint mSeekbarProgressPaint = null;
    private RectF mArcRectF = null;

    private boolean mIsShowProgressText = false;
    private Paint mProgressTextPaint = null;
    private int mProgressTextSize = 0;

    private int mViewHeight = 0;
    private int mViewWidth = 0;
    private int mSeekBarSize = 0;
    private int mSeekBarRadius = 0;
    private int mSeekBarCenterX = 0;
    private int mSeekBarCenterY = 0;
    private float mThumbLeft = 0;
    private float mThumbTop = 0;

    private float mSeekBarDegree = 0;
    private int mCurrentProgress = 0;

    public CircleSeekBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
        mAttrs = attrs;
        initView();
    }

    public CircleSeekBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        mAttrs = attrs;
        initView();
    }

    public CircleSeekBar(Context context) {
        super(context);
        mContext = context;
        initView();
    }

    private void initView(){
        if(DEBUG) Log.d(TAG, "initView");
        TypedArray localTypedArray = mContext.obtainStyledAttributes(mAttrs, R.styleable.CircleSeekBar);

        //thumb的属性是使用android:thumb属性进行设置的
        //返回的Drawable为一个StateListDrawable类型,即可以实现选中效果的drawable list
        //mThumbNormal和mThumbPressed则是用于设置不同状态的效果,当点击thumb时设置mThumbPressed,否则设置mThumbNormal
        mThumbDrawable = localTypedArray.getDrawable(R.styleable.CircleSeekBar_android_thumb);
        mThumbWidth = this.mThumbDrawable.getIntrinsicWidth();
        mThumbHeight = this.mThumbDrawable.getIntrinsicHeight();

        mThumbNormal = new int[]{-android.R.attr.state_focused, -android.R.attr.state_pressed,
                -android.R.attr.state_selected, -android.R.attr.state_checked};
        mThumbPressed = new int[]{android.R.attr.state_focused, android.R.attr.state_pressed,
                android.R.attr.state_selected, android.R.attr.state_checked};

        float progressWidth = localTypedArray.getDimension(R.styleable.CircleSeekBar_progress_width, 5);
        int progressBackgroundColor = localTypedArray.getColor(R.styleable.CircleSeekBar_progress_background, Color.GRAY);
        int progressFrontColor = localTypedArray.getColor(R.styleable.CircleSeekBar_progress_front, Color.BLUE);
        mSeekBarMax = localTypedArray.getInteger(R.styleable.CircleSeekBar_progress_max, 100);

        mSeekbarProgressPaint = new Paint();
        mSeekBarBackgroundPaint = new Paint();

        mSeekbarProgressPaint.setColor(progressFrontColor);
        mSeekBarBackgroundPaint.setColor(progressBackgroundColor);

        mSeekbarProgressPaint.setAntiAlias(true);
        mSeekBarBackgroundPaint.setAntiAlias(true);

        mSeekbarProgressPaint.setStyle(Paint.Style.STROKE);
        mSeekBarBackgroundPaint.setStyle(Paint.Style.STROKE);

        mSeekbarProgressPaint.setStrokeWidth(progressWidth);
        mSeekBarBackgroundPaint.setStrokeWidth(progressWidth);

        mArcRectF = new RectF();

        mIsShowProgressText = localTypedArray.getBoolean(R.styleable.CircleSeekBar_show_progress_text, false);
        int progressTextStroke = (int) localTypedArray.getDimension(R.styleable.CircleSeekBar_progress_text_stroke_width, 5);
        int progressTextColor = localTypedArray.getColor(R.styleable.CircleSeekBar_progress_text_color, Color.GREEN);
        mProgressTextSize = (int) localTypedArray.getDimension(R.styleable.CircleSeekBar_progress_text_size, 50);
        mProgressTextPaint = new Paint();
        mProgressTextPaint.setColor(progressTextColor);
        mProgressTextPaint.setAntiAlias(true);
        mProgressTextPaint.setStrokeWidth(progressTextStroke);
        mProgressTextPaint.setTextSize(mProgressTextSize);

        localTypedArray.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(DEBUG) Log.d(TAG, "onMeasure");
        mViewWidth = getWidth();
        mViewHeight = getHeight();

        mSeekBarSize = mViewWidth > mViewHeight ? mViewHeight : mViewWidth;

        mSeekBarCenterX = mViewWidth / 2;
        mSeekBarCenterY = mViewHeight / 2;

        mSeekBarRadius = mSeekBarSize / 2 - mThumbWidth / 2;

        int left = mSeekBarCenterX - mSeekBarRadius;
        int right = mSeekBarCenterX + mSeekBarRadius;
        int top = mSeekBarCenterY - mSeekBarRadius;
        int bottom = mSeekBarCenterY + mSeekBarRadius;
        mArcRectF.set(left, top, right, bottom);

        // 起始位置,三点钟方向
        setThumbPosition(Math.toRadians(mSeekBarDegree));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawCircle(mSeekBarCenterX, mSeekBarCenterY, mSeekBarRadius,
                mSeekBarBackgroundPaint);
        canvas.drawArc(this.mArcRectF, 0.0F, mSeekBarDegree, false, mSeekbarProgressPaint);
        drawThumbBitmap(canvas);
        drawProgressText(canvas);

        super.onDraw(canvas);
    }

    private void drawThumbBitmap(Canvas canvas) {
        this.mThumbDrawable.setBounds((int) mThumbLeft, (int) mThumbTop,
                (int) (mThumbLeft + mThumbWidth), (int) (mThumbTop + mThumbHeight));
        this.mThumbDrawable.draw(canvas);
    }

    private void drawProgressText(Canvas canvas) {
        if (true == mIsShowProgressText){
            float textWidth = mProgressTextPaint.measureText("" + mCurrentProgress);
            canvas.drawText("" + mCurrentProgress, mSeekBarCenterX - textWidth / 2, mSeekBarCenterY
                    + mProgressTextSize / 2, mProgressTextPaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float eventX = event.getX();
        float eventY = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                seekTo(eventX, eventY, false);
                break ;

            case MotionEvent.ACTION_MOVE:
                seekTo(eventX, eventY, false);
                break ;

            case MotionEvent.ACTION_UP:
                seekTo(eventX, eventY, true);
                break ;
        }
        return true;
    }

    private void seekTo(float eventX, float eventY, boolean isUp) {
        if (true == isPointOnThumb(eventX, eventY) && false == isUp) {
            mThumbDrawable.setState(mThumbPressed);
            double radian = Math.atan2(eventY - mSeekBarCenterY, eventX - mSeekBarCenterX);
            /*
             * 由于atan2返回的值为[-pi,pi]
             * 因此需要将弧度值转换一下,使得区间为[0,2*pi]
             */
            if (radian < 0){
                radian = radian + 2*Math.PI;
            }
            if(DEBUG) Log.e(TAG, "seekTo radian = " + radian);
            setThumbPosition(radian);

            mSeekBarDegree = (float) Math.round(Math.toDegrees(radian));
            mCurrentProgress = (int) (mSeekBarMax * mSeekBarDegree / 360);
            invalidate();
        }else{
            mThumbDrawable.setState(mThumbNormal);
            invalidate();
        }
    }

    private boolean isPointOnThumb(float eventX, float eventY) {
        boolean result = false;
        double distance = Math.sqrt(Math.pow(eventX - mSeekBarCenterX, 2)
                + Math.pow(eventY - mSeekBarCenterY, 2));
        if (distance < mSeekBarSize && distance > (mSeekBarSize / 2 - mThumbWidth)){
            result = true;
        }
        return result;
    }

    private void setThumbPosition(double radian) {
        if(DEBUG) Log.v(TAG, "setThumbPosition radian = " + radian);
        double x = mSeekBarCenterX + mSeekBarRadius * Math.cos(radian);
        double y = mSeekBarCenterY + mSeekBarRadius * Math.sin(radian);
        mThumbLeft = (float) (x - mThumbWidth / 2);
        mThumbTop = (float) (y - mThumbHeight / 2);
    }

    /*
     * 增加set方法,用于在java代码中调用
     */
    public void setProgress(int progress) {
        if(DEBUG) Log.v(TAG, "setProgress progress = " + progress);
        if (progress > mSeekBarMax){
            progress = mSeekBarMax;
        }
        if (progress < 0){
            progress = 0;
        }
        mCurrentProgress = progress;
        mSeekBarDegree = (progress * 360 / mSeekBarMax);
        if(DEBUG) Log.d(TAG, "setProgress mSeekBarDegree = " + mSeekBarDegree);
        setThumbPosition(Math.toRadians(mSeekBarDegree));

        invalidate();
    }

    public int getProgress(){
        return mCurrentProgress;
    }

    public void setProgressMax(int max){
        if(DEBUG) Log.v(TAG, "setProgressMax max = " + max);
        mSeekBarMax = max;
    }

    public int getProgressMax(){
        return mSeekBarMax;
    }

    public void setProgressThumb(int thumbId){
        mThumbDrawable = mContext.getResources().getDrawable(thumbId);
    }

    public void setProgressWidth(int width){
        if(DEBUG) Log.v(TAG, "setProgressWidth width = " + width);
        mSeekbarProgressPaint.setStrokeWidth(width);
        mSeekBarBackgroundPaint.setStrokeWidth(width);
    }

    public void setProgressBackgroundColor(int color){
        mSeekBarBackgroundPaint.setColor(color);
    }

    public void setProgressFrontColor(int color){
        mSeekbarProgressPaint.setColor(color);
    }

    public void setProgressTextColor(int color){
        mProgressTextPaint.setColor(color);
    }

    public void setProgressTextSize(int size){
        if(DEBUG) Log.v(TAG, "setProgressTextSize size = " + size);
        mProgressTextPaint.setTextSize(size);
    }

    public void setProgressTextStrokeWidth(int width){
        if(DEBUG) Log.v(TAG, "setProgressTextStrokeWidth width = " + width);
        mProgressTextPaint.setStrokeWidth(width);
    }

    public void setIsShowProgressText(boolean isShow){
        mIsShowProgressText = isShow;
    }
}

xml文件如下

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:circle_seekbar="http://schemas.android.com/apk/res/com.lee.circleseekbar"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.lee.circleseekbar.view.CircleSeekBar
        android:id="@+id/circle_seekbar"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerInParent="true"
        android:thumb="@drawable/thumb"
        circle_seekbar:progress_background="@android:color/darker_gray"
        circle_seekbar:progress_front="@android:color/holo_blue_light"
        circle_seekbar:progress_max="1000"
        circle_seekbar:progress_text_color="@android:color/holo_green_light"
        circle_seekbar:progress_text_size="30dp"
        circle_seekbar:progress_text_stroke_width="4dp"
        circle_seekbar:progress_width="2dp"
        circle_seekbar:show_progress_text="true" />

</RelativeLayout>

attrs属性文件如下

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="CircleSeekBar">
        <attr name="android:thumb" />
        <attr name="progress_width" format="dimension" />
        <attr name="progress_background" format="color" />
        <attr name="progress_front" format="color" />
        <attr name="progress_max" format="integer" />
        <attr name="show_progress_text" format="boolean" />
        <attr name="progress_text_stroke_width" format="dimension" />
        <attr name="progress_text_color" format="color" />
        <attr name="progress_text_size" format="dimension" />
    </declare-styleable>

</resources>

activity文件下

package com.lee.circleseekbar;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import com.lee.circleseekbar.view.CircleSeekBar;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        CircleSeekBar circleSeekBar = (CircleSeekBar) findViewById(R.id.circle_seekbar);
        circleSeekBar.setProgress(100);
        circleSeekBar.setProgressFrontColor(Color.RED);
        //circleSeekBar.setProgressWidth(20);

    }

}

Demo下载地址

http://download.csdn.net/detail/ly0904010214/8370879



时间: 2024-11-08 23:55:53

自定义View实现环形SeekBar的相关文章

Android进阶——自定义View之自己绘制彩虹圆环调色板

引言 前面几篇文章都是关于通过继承系统View和组合现有View来实现自定义View的,刚好由于项目需要实现一个滑动切换LED彩灯颜色的功能,所以需要一个类似调色板的功能,随着手在调色板有效区域滑动,LED彩灯随即显示相应的颜色,也可以通过左右的按钮,按顺序切换显示一组颜色,同时都随着亮度的改变LED彩灯的亮度随即变化,这篇基本上把继承View重绘实现自定义控件的大部分知识总结了下(当然还有蛮多没有涉及到,比如说自适应布局等),源码在Github上 一.继承View绘制自定义控件的通用步骤 自定

自定义view画图

在实现自定义View之前,有必要掌握Android中画图的相关类的使用方法,这是自定义各种酷炫界面的基础.主要使用到以下两个类: 画笔:Paint 画布:Canvas 1. Android中的Paint和Canvas的概念和使用方法 Android中的Paint和Canvas的概念是很简单的,就是我们用画笔在画布上进行绘制,没什么难度的,我们只要拿到画笔Paint和画布Canvas对象就可以进行操作了.当然Canvas对象提供了很多绘制图形的方法, 1.1 Paint对象 新建一个Paint画笔

自定义view—折线图

学习导航 第一节:http://blog.csdn.net/bobo8945510/article/details/53197727 -自定义View-自定义属性及引用 第二节:http://blog.csdn.net/bobo8945510/article/details/53203233 自定义view02-图形绘制 第三节:http://blog.csdn.net/bobo8945510/article/details/53213938 自定义View-绘图基础之Path 第四节:http

Android 自定义带刻度的seekbar

自定义带刻度的seekbar 1.布局 <span style="font-family:SimHei;font-size:18px;"><com.imibaby.client.views.CustomSeekbar android:id="@+id/myCustomSeekBar" android:layout_width="wrap_content" android:layout_height="wrap_cont

Android 如何 画 柱状图 -------自定义View

实现了 柱状图 根据 SeekBar的滑动 改变的效果: 图示效果: 自定义View的代码: package com.example.coustomviewdemo; import android.R.color; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.g

自定义View系列教程04--Draw源码分析及其实践

通过之前的详细分析,我们知道:在measure中测量了View的大小,在layout阶段确定了View的位置. 完成这两步之后就进入到了我们相对熟悉的draw阶段,在该阶段真正地开始对视图进行绘制. 按照之前的惯例,我们来瞅瞅View中draw( )的源码 public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFL

Android自定义竖直方向SeekBar

写在前面 因为有这样的一个场景,需要实现竖直方向的多色进度条,然后在网上也找了下,没看到符合需要的,于是自定义了一个,效果如下: 具体实现 本来想定义水平的,然后旋转一下,后来发现还不如直接定义竖直方向来的直接,就直接在竖直方向画了下. 首先讲一下思路,就是通过继承View,然后通过onDraw()方法进行绘制.具体绘制的时候,需要处理一些小细节.比如,我们需要画一个圆形的滑动块,那么我们的背景色带就不能把整个宽度占满,要不然,小圆块只能和色带一样宽了,效果不是很好看,所以在绘制的时候应该把背景

自定义view实现涂鸦(画板)功能

自定义view实现涂鸦功能,包括撤销.恢复.重做.保存以及橡皮擦(在风格中实现)功能,小模块包括画笔颜色调整.画笔尺寸调整.画笔类型(包括正常画笔以及橡皮擦功能),之后又陆续实现了画圆.画矩形以及画箭头的功能,这里我们先完成前面的需求 撤销: /** * 撤销 * 撤销的核心思想就是将画布清空, * 将保存下来的Path路径最后一个移除掉, * 重新将路径画在画布上面. */ public void undo() { if (savePath != null && savePath.siz

自定义view(二)

这里是自定义view(二),上一篇关于自定义view的一些基本知识,比如说自定义view的步骤.会涉及到哪些函数以及如何实现自定义属性,同时实现了一个很基础的自定义控件,一个自定义的计时器,需要看的人可以点击这个链接:http://www.cnblogs.com/YaoJianXun/p/5806926.html. 这次讲的是如何通过坐标系的变化实现一些更复杂的自定义view绘制,上一次博客我们实现了一个类似于计时器的环形控件,这次我们在那个基础上再做一次改动,通过坐标系的变动实现下面的效果: