自定义View。仿圆形时间选择器

转载请注明出处:http://blog.csdn.net/liu470368500/article/details/40377421


最近在机器上发现原生的闹钟里有个圆形的时间选择器。感觉效果挺不错的。一时心痒想起自己模仿着写一个类似的。也算是练练手。


如上图所示。红色部分可以跟随手指滑动而选择移动到相应的位置。就像钟表的指针一样。

下面问题来了。如果要自定义一个出来。应该怎么实现呢?

1.数字需要以圆环形式排列。那么就需要计算每个item之间的弧度。然后根据三角函数使用此弧度来定位显示的位置

2.要跟随手指移动。则需要根据手指触摸的位置与圆心坐标来计算出手指触摸处需要转动的弧度。以此来设置当前选中的position是哪个。

貌似需要的技术就这么点。。。

废话不多说。直接上源码:

TimeCircleSelector类:

package com.liu.timecircleselector;

import android.content.Context;
import android.content.res.TypedArray;
import android.filterfw.geometry.Point;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;


public class TimeCircleSelector extends View {

	/**
	 * 自身的宽高
	 */
	int mWidth = 0;
	int mHeight = 0;

	/**
	 * 圆环半径
	 */
	private float mCircleRadius = 0;

	/**
	 * 数字显示的位置距离圆心坐标的距离
	 */
	private float mCircleInnerRadius = 0;

	/**
	 * 圆心半径
	 */
	private float mCircleCenterRadius = 0;

	/**
	 * 圆心颜色
	 */
	private int mCircleCenterColor = 0;

	/**
	 * 指针宽度
	 */
	private int mCirclePointWidth = 0;

	/**
	 * 指针颜色
	 */
	private int mCirclePointColor = 0;

	/**
	 * 选中项下的圆形背景半径
	 */
	private int mCircleTargetRadius = 0;

	/**
	 * 选中项下的圆形背景颜色
	 */
	private int mCircleTargetColor = 0;

	/**
	 * 边距
	 */
	private int margin = 40;
	private Paint mPaint = null;

	/**
	 * 一圈有多少个选择项
	 */
	private int mCircleCount = 24;

	/**
	 * 相邻两个选项间的弧度
	 */
	private float mSingleRadian = 0;

	/**
	 * 中心点坐标
	 */
	private Point mCenterPoint = null;

	/**
	 * 无效的位置
	 */
	private final int INVALID_POSITION = -1;

	/**
	 * 当前选择的位置
	 */
	private int mSelectPosition = 2;

	/**
	 * 环形时间选择器的数据适配器
	 */
	private TimeAdapter mAdapter;

	private OnSelectionChangeListener mSelectionChangeListener;

	/* =================get/set方法====================== */

	/**
	 * 设置一圈有多少个item
	 */
	public void setCircleCount(int cirlceCount) {
		mCircleCount = cirlceCount;
		reset();
	}

	/**
	 * 设置数据适配器
	 */
	public void setAdapter(TimeAdapter adapter) {
		mAdapter = adapter;
		setCircleCount(mAdapter.getCount());
	}

	/**
	 * 设置位置改变的监听
	 */
	public void setOnSelectionChangeListener(
			OnSelectionChangeListener selectionChangeListener) {
		mSelectionChangeListener = selectionChangeListener;
	}

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

	public TimeCircleSelector(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public TimeCircleSelector(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.Circle_Selector);
		mCircleCenterColor = a.getColor(
				R.styleable.Circle_Selector_circle_center_color, Color.BLUE);
		mCirclePointColor = a.getColor(
				R.styleable.Circle_Selector_circle_point_color, Color.RED);
		mCircleTargetColor = a.getColor(
				R.styleable.Circle_Selector_circle_center_color, Color.GREEN);
		mCircleCenterRadius = a.getInt(
				R.styleable.Circle_Selector_circle_center_radius, 5);
		mCirclePointWidth = a.getInt(
				R.styleable.Circle_Selector_circle_point_width, 5);
		mCircleTargetRadius = a.getInt(
				R.styleable.Circle_Selector_circle_target_radius, 40);
		a.recycle();

		init();
	}

	private void init() {
		mCenterPoint = new Point();
		mPaint = new Paint();
		mPaint.setAlpha(80);
		mPaint.setAntiAlias(true);
		mPaint.setColor(Color.CYAN);
		reset();
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		mWidth = w;
		mHeight = h;
		// 计算出中心坐标值
		mCenterPoint.x = w / 2;
		mCenterPoint.y = h / 2;
		// 计算圆形半径
		int minSize = w > h ? h : w;
		mCircleRadius = (minSize - 2 * margin) / 2;
		// 圆心半径
		mCircleCenterRadius = mCircleRadius * .05f;
		mCircleInnerRadius = mCircleRadius* .8f;
		super.onSizeChanged(w, h, oldw, oldh);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		int action = event.getAction();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
		case MotionEvent.ACTION_MOVE:
			getTouchPositionFromPoint(event.getX(), event.getY());
			break;
		}
		return true;
	}

	private void reset() {
		// 计算相邻两个item之间的弧度距离
		mSingleRadian = (float) ((2 * Math.PI) / mCircleCount);
		mSelectPosition = mCircleCount;
	}

	/**
	 * 根据点击的坐标点获取是点击在哪个position的区域
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	private int getTouchPositionFromPoint(float x, float y) {
		double radians = getRadian(x, y);
		System.out.println("===radians===" + radians);

		if (radians <= mSingleRadian / 2
				|| radians >= (2 * Math.PI) - mSingleRadian / 2) {
			// 顶上最中央的地方需要特殊处理。
			setSelection(mCircleCount);
			return mCircleCount;
		}

		// 最小与最大弧度边界
		double minRadianRange = 0;
		double maxRadianRange = 0;
		for (int i = 1; i <= mCircleCount; i++) {
			minRadianRange = mSingleRadian * (i - .5);
			maxRadianRange = mSingleRadian * (i + .5);
			if (radians > minRadianRange && radians <= maxRadianRange) {
				setSelection(i);
				return i;
			}
		}
		return mSelectPosition;
	}

	/**
	 * 设置选中的位置
	 * 
	 * @param position
	 */
	public void setSelection(int position) {
		if (mSelectionChangeListener != null) {
			mSelectionChangeListener
					.onPositionChange(position, mSelectPosition);
		}
		mSelectPosition = position;
		invalidate();
	}

	/**
	 * 获取当前的弧度
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	private double getRadian(float x, float y) {
		float disX = x - mCenterPoint.x;
		float disY = mCenterPoint.y - y;
		double radians = 0;
		if (disX > 0 && disY > 0) {
			// 第一象限
			radians = Math.atan(disX / disY);
		} else if (disX > 0 && disY < 0) {
			// 第二象限
			radians = Math.atan(disY / -disX);
			radians += Math.PI / 2;
		} else if (disX < 0 && disY <= 0) {
			// 第三象限
			radians = Math.atan(-disX / -disY);
			radians += Math.PI;
		} else if (disX < 0 && disY >= 0) {
			// 第四象限
			radians = Math.atan(disY / -disX);
			radians += Math.PI * 3 / 2;
			// 以下是点击的坐标点在以圆心为坐标原点的坐标轴上的情况
		} else if (disX == 0 && disY > 0) {
			// 在Y正轴上
			radians = 0;
		} else if (disX == 0 && disY < 0) {
			// 在Y负轴上
			radians = Math.PI;
		} else if (disX > 0 && disY == 0) {
			// 在X正轴上
			radians = Math.PI / 2;
		} else if (disX < 0 && disY == 0) {
			// 在X负轴上
			radians = Math.PI * 2 / 3;
		}
		return radians;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// 先画背景的圆
		drawOutterCircle(canvas);
		// 画圆心
		drawCircleCenter(canvas);
		// 画指针
		drawPoint(canvas);
		// 画圆周的条目
		drawCircleItem(canvas);
	}

	/**
	 * 画指针
	 */
	private void drawPoint(Canvas canvas) {
		float x = 0;
		float y = 0;
		if (mSelectPosition != INVALID_POSITION) {
			mPaint.setColor(mCirclePointColor);
			mPaint.setStrokeWidth(mCirclePointWidth);
			x = mCenterPoint.x
					+ (int) (mCircleInnerRadius * Math.sin(mSingleRadian
							* mSelectPosition));
			y = mCenterPoint.y
					- (int) (mCircleInnerRadius * Math.cos(mSingleRadian
							* mSelectPosition));
			canvas.drawLine(mCenterPoint.x, mCenterPoint.y, x, y, mPaint);
			mPaint.setColor(mCircleCenterColor);
			canvas.drawCircle(mCenterPoint.x, mCenterPoint.y,
					mCircleCenterRadius, mPaint);
			mPaint.setColor(mCircleTargetColor);
			canvas.drawCircle(x, y, mCircleTargetRadius, mPaint);

		}
	}

	/**
	 * 画圆周条目
	 */
	private void drawCircleItem(Canvas canvas) {
		mPaint.setColor(Color.BLUE);
		int txtSize = dip2px(getContext(), 15);
		System.out.println("==txtSize=" + txtSize);
		mPaint.setTextSize(txtSize);
		float x = 0;
		float y = 0;
		// 绘制的item文字距离圆心的位置的长度
		mPaint.setColor(Color.BLUE);
		for (int i = 1; i <= mCircleCount; i++) {
			String text = mAdapter == null ? ("" + i) : mAdapter
					.getNameByPosition(i);
			float textW = mPaint.measureText(text);
			x = (int) (mCenterPoint.x + mCircleInnerRadius
					* Math.sin(mSingleRadian * i) - textW / 2);
			y = (int) (mCenterPoint.y - mCircleInnerRadius
					* Math.cos(mSingleRadian * i) + txtSize / 2);
			canvas.drawText(text, x, y, mPaint);
		}
	}

	/**
	 * 画圆心
	 */
	private void drawCircleCenter(Canvas canvas) {
		mPaint.setColor(mCircleCenterColor);
		canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mCircleCenterRadius,
				mPaint);
	}

	/**
	 * 画背景的圆
	 */
	private void drawOutterCircle(Canvas canvas) {
		mPaint.setColor(Color.GRAY);
		canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mCircleRadius, mPaint);
	}

	/**
	 * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
	 */
	public static int dip2px(Context context, float dpValue) {
		final float scale = context.getResources().getDisplayMetrics().density;
		return (int) (dpValue * scale + 0.5f);
	}

	public interface OnSelectionChangeListener {

		void onPositionChange(int newPositoin, int oldPosition);

	}

	public interface TimeAdapter {
		int getCount();

		String getNameByPosition(int position);

	}

}

自定义属性:

    <!-- 环形选择器的自定义属性 -->
    <declare-styleable name="Circle_Selector">
        <!-- 圆心的半径 -->
        <attr name="circle_center_radius" format="integer"/>
        <!-- 圆心颜色 -->
        <attr name="circle_center_color" format="color"/>
        <!-- 指针宽度 -->
        <attr name="circle_point_width" format="integer"/>
        <!-- 指针颜色 -->
        <attr name="circle_point_color" format="color"/>
        <!-- 选中项下的圆形背景半径 -->
        <attr name="circle_target_radius" format="integer"/>
        <!-- 选中项下的圆形背景颜色 -->
        <attr name="circle_target_color" format="color"/>
    </declare-styleable>

布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:circle="http://schemas.android.com/apk/res/com.liu.timecircleselector"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView 
        android:id="@+id/circleSelectTv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />
    
    <com.liu.timecircleselector.TimeCircleSelector 
        android:id="@+id/circleSelect"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        circle:circle_center_radius="10"
        circle:circle_center_color="#FFFF35FF"
        circle:circle_point_width="5"
        circle:circle_point_color="#FF00FFFF"
        circle:circle_target_radius="40"
        circle:circle_target_color="#3000FFFF"
        />

</LinearLayout>


Activity中使用方法:

public class MainActivity extends Activity implements TimeAdapter,
		OnSelectionChangeListener {
	private TimeCircleSelector circleSelect;
	private TextView circleSelectTv;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.circle_select_layout);
		circleSelect = (TimeCircleSelector) findViewById(R.id.circleSelect);
		circleSelectTv = (TextView) findViewById(R.id.circleSelectTv);
		circleSelect.setAdapter(this);
		circleSelect.setOnSelectionChangeListener(this);
	}

	@Override
	public String getNameByPosition(int position) {
		if (position % 5 == 0) {
			return position + "";
		}
		return "";
	}

	@Override
	public void onPositionChange(int newPositoin, int oldPosition) {
		circleSelectTv.setText("当前位置为:" + newPositoin);
	}

	@Override
	public int getCount() {
		return 60;
	}

}

个人觉得注释已经加得差不多了。能说的也基本加了注释了。就不再说了。如果有疑问的请在下方留言。直接上效果图。无奈个人无审美。。。咳咳。。。清喷。。。


点击下载资源


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
### 回答1: Android自定义View圆形刻度在实现上相对简单,主要步骤如下: 1. 创建一个继承自View自定义View类,命名为CircleScaleView。 2. 在该自定义View的构造方法中完成必要的初始化工作,例如设置画笔、设置View的宽高、设置绘制模式等。 3. 重写onMeasure()方法,设置View的尺寸大小。可以根据自定义的需求来决定View的宽高。 4. 重写onDraw()方法,完成绘制整个圆形刻度的逻辑。 5. 在onDraw()方法中,首先通过getMeasuredWidth()和getMeasuredHeight()方法获取到View的宽高,然后计算圆心的坐标。 6. 接着,使用Canvas对象的drawArc()方法来绘制圆弧,根据需求设置圆弧的起始角度和扫描角度。 7. 再然后,通过循环绘制每个刻度线,可以使用Canvas对象的drawLine()方法来绘制。 8. 最后,根据需要绘制刻度值或其他其他附加元素,例如圆心的标记。 9. 至此,整个圆形刻度的绘制逻辑就完成了。 10. 在使用该自定义View的时候,可以通过添加该View到布局文件中或者在代码中动态添加,并按需设置相应的属性。 需要注意的是,自定义圆形刻度的具体样式和行为取决于项目需求,上述步骤仅为基础实现框架,具体细节需要根据实际情况进行相应的调整。 ### 回答2: 在Android中实现一个圆形刻度的自定义View有几个步骤。 首先,我们需要创建一个自定义View类,继承自View或者它的子类(如ImageView)。 接下来,在自定义View的构造方法中,初始化一些必要的属性,比如画笔的颜色、宽度等。我们可以使用Paint类来设置这些属性。 然后,我们需要在自定义View的onMeasure方法中设置View的宽度和高度,确保View在屏幕上正常显示。一种常见的实现方式是将宽度和高度设置为相同的值,使得View呈现出圆形的形状。 接着,在自定义View的onDraw方法中,我们可以利用画笔来绘制圆形刻度。可以使用canvas.drawCircle方法来绘制一个圆形,使用canvas.drawLine方法绘制刻度线。我们可以根据需要,定义不同的刻度颜色和宽度。 最后,我们可以在自定义View的其他方法中,添加一些额外的逻辑。比如,在onTouchEvent方法中处理触摸事件,以实现拖动刻度的功能;在onSizeChanged方法中根据View的尺寸调整刻度的大小等等。 当我们完成了自定义View的代码编写后,我们可以在布局文件中使用这个自定义View。通过设置布局文件中的属性,可以进一步自定义View的外观和行为。 总之,实现一个圆形刻度的自定义View,我们需要定义一个自定义View类,并在其中使用画笔来绘制圆形和刻度。通过处理一些事件和属性,我们可以实现更多的功能和样式。以上就是简单的步骤,可以根据需要进行更加详细的实现。 ### 回答3: Android自定义View圆形刻度可以通过以下步骤实现。 首先,我们需要创建一个自定义View,继承自View类,并重写onDraw方法。在该方法中,我们可以自定义绘制的内容。 其次,我们需要定义一个圆形的刻度尺背景,可以使用Canvas类提供的drawCircle方法来绘制实心圆或空心圆。 接着,我们可以通过Canvas类的drawLine方法来绘制刻度线。根据刻度的数量,可以计算出每个刻度之间的角度,然后循环绘制出所有的刻度线。 然后,我们可以通过Canvas类的drawText方法来绘制刻度的值。根据刻度线的角度和半径,可以计算出刻度的坐标,然后将刻度的值绘制在指定的位置上。 最后,我们可以通过在自定义View的构造方法中获取相关的参数,如刻度的最大值、最小值、当前值等,然后根据这些参数来计算刻度的位置和值。 在使用自定义View时,可以通过设置相关的属性来改变刻度的样式和位置。例如,可以设置刻度线的颜色、粗细、长度等,也可以设置刻度值的颜色、大小等。 通过以上步骤,我们就可以实现一个圆形刻度尺的自定义View。在使用时,可以根据需要自行调整绘制的样式和逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值