自定义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. 5.Git版本库创建

    1.什么是版本库呢? 什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改.删除,Git都能跟踪,以便任何 ...

  2. git学习------>如何汉化GitLab?

    在上一篇博客中,已经正常安装好了GitLab,然而全部界面都是纯英文的,为了照顾整个团队的英文水平,因此这篇博客的目的是将纯英文的GitLab进行汉化. 纯英文界面 第一步: 确认GitLab版本号 ...

  3. python迟邦定

    1.绑定 将函数体和函数调用关联起来,就叫绑定 2.迟绑定 在程序运行之前(也就是编译和链接时)执行的绑定是早绑定,迟绑定(late binding)是发生在运行时. 3.实例说明 def outer ...

  4. php与oracle11g经典分页

    <?php $t1 = xdebug_time_index(); $conn = oci_connect("SCOTT","TIGER","19 ...

  5. 解析button和input type=”button”的区别

    一.定义和用法 <button> 标签定义的是一个按钮. 在 button 元素内部,可以放置文本或图像.这是<button>与使用 input 元素创建的按钮的不同之处. 二 ...

  6. Oracle记录登录失败的触发器

    前言:实现的功能主要是,oracle登录成功记录登录用户ip地址,登录失败记录登录失败ip地址 1,需要建立一个触发器记录登录成功的客户端用户的ip地址 大家都知道在v$session 中记录着客户端 ...

  7. Code signing is required for product type 'Application' in SDK 'iOS 11.2'

    在打包的时候出现这样一个错误,Code signing is required for product type 'Application' in SDK 'iOS 11.2'  ,就是说代码签名证书 ...

  8. Linux系统——最小化安装

    一.虚拟机进行Linux minimal 安装 网络连接:选择“自定义”——>VMnet8(NAT模式) #PC与NAT网络的虚拟机在不同网段,此时虚拟网卡作为网关建立通信 NAT模式可直接上I ...

  9. Django小练习

    1.获取系统所有Url from django.urls.resolvers import RegexURLPattern #定义函数 def get_all_url(patterns, prev, ...

  10. spoj8222

    地址: 题目: NSUBSTR - Substrings no tags  You are given a string S which consists of 250000 lowercase la ...