Android自定义view-CircleSeekbar
自定义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的更多相关文章
- Android自定义View 画弧形,文字,并增加动画效果
一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类 B ...
- (转)[原] Android 自定义View 密码框 例子
遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...
- Android 自定义View合集
自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...
- Android 自定义View (五)——实践
前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...
- Android 自定义 view(四)—— onMeasure 方法理解
前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...
- Android 自定义 view(三)—— onDraw 方法理解
前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...
- Android 自定义view(二) —— attr 使用
前言: attr 在前一篇文章<Android 自定义view -- attr理解>已经简单的进行了介绍和创建,那么这篇文章就来一步步说说attr的简单使用吧 自定义view简单实现步骤 ...
- Android 自定义View
Android 自定义View流程中的几个方法解析: onFinishInflate():从布局文件.xml加载完组件后回调 onMeasure() :调用该方法负责测量组件大小 onSizeChan ...
- Android自定义View之CircleView
Android自定义View之CircleView 版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请表明出处:http://www.cnblogs.com/cavalier-/p/5999 ...
- Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程
转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...
随机推荐
- php的soap无故出错的真凶:wsdl缓存
soap不更新的真凶: 当我修改了服务端的调用方法后,更新了wsdl,客户端的参数老是丢失(不是全丢失,而是部分),让人很郁闷. 第二天我索性增加了一个服务端的新方法,更新了wsdl,但是客户端请求却 ...
- selinux-网络服务安全
一.显示和设置selinux [root@localhost ~]# vim /etc/sysconfig/selinux //强制模式 许可模式 禁用模式 [root@localhost ~]# g ...
- keeplived + mysql双主复制部署 --原创
环境: master 1: 192.168.100.10 oracle linux 7.4 mysql 5.7.1 master 2: 192.168.100.11 oracle linux ...
- listview与adapter用法
Android listview与adapter用法 listview与adapter用法 博客分类: android 一个ListView通常有两个职责. (1)将数据填充到布局. (2)处理用 ...
- Jquery each循环用法小结
var str = res.ZhaoPian; var piclist = str.substring(0, str.length - 1).split(','); $.each(piclist, f ...
- 2-AMD
诞生背景1.随着前端逻辑越来越多,项目越来越大,开发大型项目就必须分模块开发2.一切都那么完美,在NodeJs实现后,当人们开始热情的打算把这种实现也用于浏览器时,却发现并不适合.NodeJS应用加载 ...
- 《Unity3D》通过对象池模式,管理场景中的元素
池管理类有啥用? 在游戏场景中,我们有时候会需要复用一些游戏物体,比如常见的子弹.子弹碰撞类,某些情况下,怪物也可以使用池管理,UI部分比如:血条.文字等等 这些元素共同的特性是:存在固定生命周期,使 ...
- NGUI混合FingerGesture《卷二》分离触摸事件
背景 在使用NGUI按钮组件的时候,点击按钮同时触发FingerGesture的Touch事件, 通俗点讲,NGUI点击之后,点击事件被FingerGesture又执行了一遍. 解决思路 持有一个全局 ...
- JQuery EasyUI 扩展方法 日期控件 设置时间段函数
/** Jquery扩展方法--by hgx 2018年1月8日-- * 设置时间段函数,开始时间(1号)与结束时间(当前日期) * 传入参数:--spaceMonth:查询间隔月,1为间隔查询一个月 ...
- 手写Bind
Function.prototype.bind2 = function(context){ var self = this; var args = [].slice.call(arguments,1) ...