自定义view练手,效果图如下:
实现功能

可设置圆环颜色和线宽及触摸后的颜色和线宽
    可设置圆环内圈显示的文本内容及字体大小、颜色
    可设置触摸点的图片
    可设置触摸的有效范围

源码github链接
使用示例

<RelativeLayout 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"
    tools:context="circlebar.oden.com.circleseekbardemo.MainActivity">

<circlebar.oden.com.circleseekbar.CircleSeekbar
        android:id="@+id/circle_seekbar"
        android:layout_centerInParent="true"
        android:layout_width="300dp"
        android:layout_height="300dp" />

<TextView
        android:id="@+id/tv_progress"
        android:text="0%"
        android:textColor="#FF383838"
        android:textSize="18sp"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

public class MainActivity extends AppCompatActivity {
    String TAG = "MainActivity";
    CircleSeekbar circleSeekbar;
    TextView tv_progress;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        circleSeekbar = (CircleSeekbar) findViewById(R.id.circle_seekbar);
        tv_progress = (TextView) findViewById(R.id.tv_progress);

circleSeekbar.setOnSeekBarChangeListener(new CircleSeekbar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(CircleSeekbar circleSeekbar, int progress, boolean fromUser) {
                Log.d(TAG, "onProgressChanged progress: " + progress);
                tv_progress.setText(progress + "%");
            }
        });

}
}

实现过程
attrs中定义属性

可设置的属性如下

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

<declare-styleable name="CircleSeekbar">
        <attr name="circleColor" format="color" />
        <attr name="progressColor" format="color" />
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="progress" format="integer" />
        <attr name="maxProgress" format="integer" />
        <attr name="progressWidth" format="dimension" />
        <attr name="circleWidth" format="dimension" />
        <attr name="thumb" format="reference" />
        <attr name="enabled" format="boolean" />
        <attr name="touchInside" format="boolean" />
    </declare-styleable>

</resources>

获取属性及初始化画笔

private void init(Context context, AttributeSet attrs) {
        float density = context.getResources().getDisplayMetrics().density;
        int thumbHalfheight = 0;
        int thumbHalfWidth = 0;

circleColor = getResources().getColor(R.color.circleseekbar_gray);
        progressColor = getResources().getColor(R.color.circleseekbar_blue_light);
        textColor = getResources().getColor(R.color.circleseekbar_text_color);
        progressWidth = (int) (progressWidth * density);
        circleWidth = (int) (circleWidth * density);
        textSize = (int) (textSize * density);

padding = (int) (padding * density);
        mThumb = getResources().getDrawable(R.drawable.seekbar_control_selector);

if (null != attrs) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleSeekbar);
            circleColor = typedArray.getColor(R.styleable.CircleSeekbar_circleColor, circleColor);
            progressColor = typedArray.getColor(R.styleable.CircleSeekbar_circleColor, progressColor);
            textColor = typedArray.getColor(R.styleable.CircleSeekbar_textColor, textColor);
            textSize = typedArray.getColor(R.styleable.CircleSeekbar_textSize, textSize);
            circleWidth = (int) typedArray.getDimension(R.styleable.CircleSeekbar_circleWidth, circleWidth);
            progressWidth = (int) typedArray.getDimension(R.styleable.CircleSeekbar_progressWidth, progressWidth);
            mProgress = typedArray.getInteger(R.styleable.CircleSeekbar_progress, mProgress);
            maxProgress = typedArray.getInteger(R.styleable.CircleSeekbar_maxProgress, maxProgress);
            mTouchInside = typedArray.getBoolean(R.styleable.CircleSeekbar_touchInside, mTouchInside);
            Drawable thumb = typedArray.getDrawable(R.styleable.CircleSeekbar_thumb);
            if (thumb != null) {
                mThumb = thumb;
            }

thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2;
            thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2;
            mThumb.setBounds(-thumbHalfWidth, -thumbHalfheight, thumbHalfWidth,
                    thumbHalfheight);
            typedArray.recycle();
        }

padding = thumbHalfheight + padding;

maxWidth = circleWidth > progressWidth ? circleWidth : progressWidth;
        mProgress = (mProgress > maxProgress) ? maxProgress : mProgress;
        mProgress = (mProgress < 0) ? 0 : mProgress;

circlePaint = new Paint();
        circlePaint.setColor(circleColor);
        circlePaint.setAntiAlias(true);
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setStrokeWidth(circleWidth);

progressPaint = new Paint();
        progressPaint.setColor(progressColor);
        progressPaint.setAntiAlias(true);
        progressPaint.setStyle(Paint.Style.STROKE);
        progressPaint.setStrokeWidth(progressWidth);

paintDegree = new Paint();
        paintDegree.setColor(textColor);
        paintDegree.setAntiAlias(true);
        paintDegree.setStyle(Paint.Style.FILL);
        paintDegree.setStrokeWidth(1);
        paintDegree.setTextSize(textSize);
    }

测量

获取圆的中心坐标、半径、以及用来画弧的RectF

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        mWidth = Math.min(width, height);
        centreX = mWidth / 2;
        centreY = mWidth / 2;

float left = getPaddingLeft() + maxWidth / 2 + padding;
        float top = getPaddingTop() + maxWidth / 2 + padding;
        float right = mWidth - getPaddingRight() - maxWidth / 2 - padding;
        float bottom = mWidth - getPaddingBottom() - maxWidth / 2 - padding;

diameter = mWidth - getPaddingLeft() - getPaddingRight() - maxWidth - padding * 2;
        radius = diameter / 2;
        mCircleRect.set(left, top, left + diameter, top + diameter);

updateThumbPosition(mCurAngle);

setTouchInSide(mTouchInside);
        setMeasuredDimension(mWidth, mWidth);
    }

绘制view

@Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.RED);
        canvas.drawCircle(mWidth / 2, mWidth / 2, radius, circlePaint);
        canvas.drawArc(mCircleRect, -90, (float) mCurAngle, false, progressPaint);
        drawText(canvas);
        canvas.translate(mThumbXPos, mThumbYPos);
        mThumb.draw(canvas);
    }

private void drawText(Canvas canvas) {
        float rotation = 360 / degreeText.length;
        for (int i = 0; i < degreeText.length; i++) {
            canvas.drawText(degreeText[i], centreX - paintDegree.measureText(degreeText[i]) / 2, 100/*字体的高度*/, paintDegree);
            canvas.rotate(rotation, centreX, centreY);
        }
    }

设置触摸事件

@Override
    public boolean onTouchEvent(MotionEvent event) {
        if (enable) {
            this.getParent().requestDisallowInterceptTouchEvent(true);//通知父控件勿拦截本次触摸事件

switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    updateOnTouch(event);
                    break;
                case MotionEvent.ACTION_MOVE:
                    updateOnTouch(event);
                    break;
                case MotionEvent.ACTION_UP:
                    isFirstTouch = true;
                    setPressed(false);
                    this.getParent().requestDisallowInterceptTouchEvent(false);
                    break;
                case MotionEvent.ACTION_CANCEL:
                    isFirstTouch = true;
                    setPressed(false);
                    this.getParent().requestDisallowInterceptTouchEvent(false);
                    break;
            }
            return true;
        }
        return false;
    }

触摸事件中计算相应的角度及进度条的值

private void updateOnTouch(MotionEvent event) {
        boolean ignoreTouch = ignoreTouch(event.getX(), event.getY());
        if (ignoreTouch) {
            return;
        }
        setPressed(true);
        getTouchDegrees(event.getX(), event.getY());
        mProgress = getProgressForAngle(mCurAngle);
        updateProgress(mProgress, true);
    }

//根据触摸点的坐标获取角度值
    private double getTouchDegrees(float xPos, float yPos) {
        float x = xPos - centreX;
        float y = yPos - centreY;

// convert to arc Angle
        double angle = Math.toDegrees(Math.atan2(y, x) + (Math.PI / 2));

if (angle < 0) {
            angle = 360 + angle;
        }

if (isOnlyScrollOneCircle) {
            if (mCurAngle > 270 && angle < 90) {
                mCurAngle = 360;
            } else if (mCurAngle < 90 && angle > 270) {
                mCurAngle = 0;
            } else {
                mCurAngle = angle;
            }
        } else {
            mCurAngle = angle;
        }
        if (isFirstTouch) {  //如果是滑动前第一次点击则总是移动到该度数
            mCurAngle = angle;
            isFirstTouch = false;
        }

Log.d(TAG, "getTouchDegrees: " + angle);
        return mCurAngle;
    }

//根据触摸的角度值获取滑动条progree的值
    private int getProgressForAngle(double angle) {
        int touchProgress = (int) Math.round(maxProgress * angle / 360);

touchProgress = (touchProgress < 0) ? INVALID_PROGRESS_VALUE
                : touchProgress;
        touchProgress = (touchProgress > maxProgress) ? INVALID_PROGRESS_VALUE
                : touchProgress;
        return touchProgress;
    }

//更新view
    private void updateProgress(int progress, boolean fromUser) {
        if (progress == INVALID_PROGRESS_VALUE) {
            return;
        }

progress = (progress > maxProgress) ? maxProgress : progress;
        progress = (progress < 0) ? 0 : progress;
        mProgress = progress;

if (onSeekBarChangeListener != null) {
            onSeekBarChangeListener.onProgressChanged(this, progress, fromUser);
        }

updateThumbPosition(mCurAngle);
        invalidate();
    }

//更新thum的坐标
    private void updateThumbPosition(double angle) {
        double cos = -Math.cos(Math.toRadians(angle));

if (angle < 180) {
            mThumbXPos = (int) (getMeasuredWidth() / 2 + Math.sqrt(1 - cos * cos) * radius);
        } else {
            mThumbXPos = (int) (getMeasuredWidth() / 2 - Math.sqrt(1 - cos * cos) * radius);
        }

mThumbYPos = (int) (centreX + radius * (float) cos);
    }

设置触摸的生效范围同时根据触摸状态同步改变thum的状态

//设置触摸生效范围
    public void setTouchInSide(boolean isEnabled) {
        int thumbHalfheight = (int) mThumb.getIntrinsicHeight() / 2;
        int thumbHalfWidth = (int) mThumb.getIntrinsicWidth() / 2;
        mTouchInside = isEnabled;
        if (mTouchInside) {
            mTouchIgnoreRadius = (float) radius / 4;
        } else {
            // Don't use the exact radius makes interaction too tricky
            mTouchIgnoreRadius = radius - Math.min(thumbHalfWidth, thumbHalfheight);
        }
    }

private boolean ignoreTouch(float xPos, float yPos) {
        boolean ignore = false;
        float x = xPos - centreX;
        float y = yPos - centreY;

float touchRadius = (float) Math.sqrt(((x * x) + (y * y)));
        if (touchRadius < mTouchIgnoreRadius) {
            ignore = true;
        }
        return ignore;
    }

//重写drawableStateChanged,同步改变thum的状态
    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        if (mThumb != null && mThumb.isStateful()) {
            int[] state = getDrawableState();
            mThumb.setState(state);
        }
        invalidate();
    }

设置监听器

public interface OnSeekBarChangeListener {
        void onProgressChanged(CircleSeekbar circleSeekbar, int progress, boolean fromUser);
    }

public void setOnSeekBarChangeListener(OnSeekBarChangeListener onSeekBarChangeListener) {
        this.onSeekBarChangeListener = onSeekBarChangeListener;
    }

Android自定义view-CircleSeekbar的更多相关文章

  1. Android自定义View 画弧形,文字,并增加动画效果

    一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类   B ...

  2. (转)[原] Android 自定义View 密码框 例子

    遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...

  3. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  4. Android 自定义View (五)——实践

    前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...

  5. Android 自定义 view(四)—— onMeasure 方法理解

    前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...

  6. Android 自定义 view(三)—— onDraw 方法理解

    前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...

  7. Android 自定义view(二) —— attr 使用

    前言: attr 在前一篇文章<Android 自定义view -- attr理解>已经简单的进行了介绍和创建,那么这篇文章就来一步步说说attr的简单使用吧 自定义view简单实现步骤 ...

  8. Android 自定义View

    Android 自定义View流程中的几个方法解析: onFinishInflate():从布局文件.xml加载完组件后回调 onMeasure() :调用该方法负责测量组件大小 onSizeChan ...

  9. Android自定义View之CircleView

    Android自定义View之CircleView 版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请表明出处:http://www.cnblogs.com/cavalier-/p/5999 ...

  10. Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...

随机推荐

  1. Python的15个坑

    1. 不要使用可变对象作为函数默认值 代码如下: In [1]: def append_to_list(value, def_list=[]):    ...:         def_list.ap ...

  2. 01-开始使用django(全、简)

    目录 (一)创建项目 1.生成django默认目录 2.创建应用目录 3.安装应用 4.配置使用mysql数据库 5.运行轻量级web服务器,预览 (二)设计模型 1.在models.py中定义模型类 ...

  3. linux环境下的python安装过程

    一.下载python源码包 打开ubuntu下的shell终端,通过wget命令下载python源码包,如下图所示: wget https://www.python.org/ftp/python/3. ...

  4. 001-shell基础,创建,运行

    一.概述 Shell 是一个用 C 语言编写的程序.Shell 既是一种命令语言,又是一种程序设计语言. Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服 ...

  5. PAT 1146 Topological Order[难]

    1146 Topological Order (25 分) This is a problem given in the Graduate Entrance Exam in 2018: Which o ...

  6. Linux下BLAST的使用---转载

    1.把BLAST的压缩文件解压,然后将bin目录下的文件拷贝至/usr/local/bin下:2.制作软链接,将解压后的文件中bin目录链接至/home/username下,eg:ln -s /hom ...

  7. 我与前端之间不得不说的三天两夜之html基础

    HTML 初识 分类 cs模式 client-server bs模式 Browser-server web服务本质 from socket import * def main(): service=s ...

  8. vue2+koa2+mongodb分页

    后端 const Koa = require('koa2'); const Router = require('koa-router'); const Monk = require('monk');/ ...

  9. 【转】Google的2012论文

    转自:http://www.sigvc.org/bbs/thread-1152-1-1.html Google的论文一直是业界的风向标,尤其在机器学习.分布式系统.网络等方面很多创新性的成果都是由他们 ...

  10. Hive的metastore

    hive --service metastore 默认端口是9083 <property> <name>hive.metastore.uris</name> < ...