android 自定义弯曲滑竿指示器
效果说明:滑竿指示器,是一段弯曲的圆弧,要求在杆上,有滑动小球事件,小球会根据下标文字的起始角度与终止角度,是否选择滑倒下一个位置。当点击下标文字时,小球也要做出相应的指示。
1)MainActivity
package com.example.chenkui.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intIndicatorTabView();
// setContentView(R.layout.watch_view);
}
private void intIndicatorTabView() {
IndicatorTabView view = (IndicatorTabView) findViewById(R.id.myView);
List<String> list = new ArrayList<>();
list.add("待评价");
list.add("待付款");
list.add("待发货");
list.add("待收货");
view.setTabInfo(list);
view.setSelection(3);//初始化选择指示器小球位置;
view.setTabChangeListener(new IndicatorTabView.OnTabChangeListener() {
@Override
public void onTabSelected(View v, int position) {
}
});
}
}
2)IndicatorTabView
在绘制时,按照图层的结构,先绘制底层颜色,再绘制上一层图形,对于特殊绘制的,如旋转画布,移动绘制圆心坐标,一般先 canvas.save();保存已经绘制图层,canvas.restore();//他的作用为,将之前的绘制保存的图片save(),进行合并.
package com.example.chenkui.myapplication;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
public class IndicatorTabView extends View {
private String TAG = IndicatorTabView.class.getSimpleName();
private static final float DEFAULT_SWEEP_ANGLE = 48.0f;
private float mSweepAngle = DEFAULT_SWEEP_ANGLE;
private float mStartAngle = (180.0f - mSweepAngle) / 2;
private List<IndicatorTabItem> mTabItems = new ArrayList<>();
private int mSelectTabIndex = -1;
private Paint mTabBackColorPaint;
private Paint mTabPaint;
private Paint mTabTtileTextPaint;//滑竿或点击标题
private Paint mTabWheelPaint;
private Paint mTabTextPaint;
private Paint mTabPointerPaint;
private float mWheelCenterX;
private float mWheelCenterY;
private float mWheelRadius;
private RectF mWheelArcRect;
private float mPointerAngle;
private float mPointerRadius;
private boolean mIsMovingPointer = false;
private OnTabChangeListener mTabChangeListener = null;
private List<IndicatorTabRectItem> mTabRectItem = new ArrayList<IndicatorTabRectItem>();
private Paint textPaint = new Paint();
private float mMinRectRadius;//文字区域,
private float mMaxRectRadius;//文字区域,
public IndicatorTabView(Context context) {
this(context, null);
}
public IndicatorTabView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public IndicatorTabView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
mTabBackColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//绘制最大的里边图层背景
mTabBackColorPaint.setStyle(Paint.Style.FILL);
mTabBackColorPaint.setColor(Color.argb(255, 35, 47, 62));
mTabPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//绘制里面最小图层背景。
mTabPaint.setStyle(Paint.Style.FILL);
mTabPaint.setColor(Color.argb(255, 253,253, 254));
mTabTtileTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//绘制里面最小图层背景。
mTabTtileTextPaint.setStyle(Paint.Style.FILL);
mTabTtileTextPaint.setColor(Color.WHITE);
mTabWheelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTabWheelPaint.setStyle(Paint.Style.STROKE);
mTabWheelPaint.setStrokeWidth(getResources().getDimension(R.dimen.tab_wheel_width));
mTabWheelPaint.setColor(Color.argb(200, 253, 250, 245));
mTabWheelPaint.setStrokeCap(Paint.Cap.ROUND);//这个是设置绘制弧是,两端圆滑;
mTabTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//绘制文字
mTabTextPaint.setColor(Color.argb(255, 253, 253, 254));
mTabTextPaint.setTextSize(getResources().getDimension(R.dimen.tab_text));
mTabTextPaint.setStrokeWidth(5);
mTabPointerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//绘制小点
mTabPointerPaint.setStyle(Paint.Style.FILL);
mTabPointerPaint.setColor(Color.argb(255, 255, 255, 255));
mPointerRadius = getResources().getDimension(R.dimen.tab_pointer_radius);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制深蓝背景。
canvas.drawCircle(mWheelCenterX, mWheelCenterY - getResources().getDimension(R.dimen.tab_wheel_padding_inner), mWheelRadius*1.2f, mTabBackColorPaint);
//绘制里面第一条弧
canvas.drawCircle(mWheelCenterX, mWheelCenterY - getResources().getDimension(R.dimen.tab_wheel_padding_inner), mWheelRadius, mTabPaint);
canvas.drawArc(mWheelArcRect, mStartAngle, mSweepAngle, false, mTabWheelPaint);//绘制滑杆
for (int i = 0; i < mTabItems.size(); i++) {
IndicatorTabItem tempItem = mTabItems.get(i);
float angle = (tempItem.getStartAngle() + tempItem.getEndAngle()) / 2 - 90.0f;
canvas.save();//将之前绘制图片保存起来,
canvas.rotate(angle, mWheelCenterX, mWheelCenterY);
canvas.drawText(tempItem.getName(), mWheelCenterX - tempItem.getMesureWidth() / 2,
getResources().getDimension(R.dimen.tab_wheel_width) + mWheelCenterY + mWheelRadius + getResources().getDimension(R.dimen.tab_wheel_padding_inner),
mTabTextPaint);
/***********************************************************************************************************/
Log.d(TAG, "-----------onDraw()-----------" + "X===[" + (mWheelCenterX - tempItem.getMesureWidth() / 2) + "]-------Y==={" + (getResources().getDimension(R.dimen.tab_wheel_width) + mWheelCenterY + mWheelRadius + getResources().getDimension(R.dimen.tab_wheel_padding_inner)) + "}");
float rectWidth = mTabTextPaint.measureText(tempItem.getName());
Paint.FontMetrics fm = mTabTextPaint.getFontMetrics();
float offsetAscent = fm.ascent;
float offsetBottom = fm.bottom;
float startX = mWheelCenterX - tempItem.getMesureWidth() / 2;
float startY = getResources().getDimension(R.dimen.tab_wheel_width) + mWheelCenterY + mWheelRadius + getResources().getDimension(R.dimen.tab_wheel_padding_inner);
Log.d(TAG, "TEXT-offsetAscent=" + offsetAscent);
// RectF testRect = new RectF(
// startX,
// (float) (startY + offsetAscent),
// (float) (startX + rectWidth),
// (float) (startY + offsetBottom)
// );
// textPaint.setColor(Color.argb(100, 233, 233, 0));
// canvas.drawRect(testRect, textPaint);
/*************************************************************************************************************************/
canvas.restore();//他的作用为,将之前的绘制保存的图片save(),进行合并.
mMinRectRadius = distance(mWheelCenterX, startY + offsetAscent);//计算
mMaxRectRadius = distance(mWheelCenterX, startY + offsetBottom);
}
float[] pointerPosition = calculatePointerPosition(mPointerAngle);
canvas.drawCircle(mWheelCenterX + pointerPosition[0], mWheelCenterY + pointerPosition[1], mPointerRadius, mTabPointerPaint);//绘制小球
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
mWheelRadius = (float) (widthSize / (2.0f * Math.sin(Math.toRadians(32))));
int offset = (int) (heightSize - getResources().getDimension(R.dimen.tab_wheel_padding_bottom));
mWheelCenterY = offset - (int) Math.sqrt(Math.pow((double) mWheelRadius, 2) - Math.pow((double) (widthSize / 2), 2));
mWheelCenterX = widthSize / 2.0f;
mWheelArcRect = new RectF(mWheelCenterX - mWheelRadius, mWheelCenterY - mWheelRadius,
mWheelCenterX + mWheelRadius, mWheelCenterY + mWheelRadius);
}
private float[] calculatePointerPosition(float angle) {
float x = (float) (mWheelRadius * Math.cos(Math.toRadians(angle)));
float y = (float) (mWheelRadius * Math.sin(Math.toRadians(angle)));
return new float[]{x, y};
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX() - mWheelCenterX;
float y = event.getY() - mWheelCenterY;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
float[] pointerPosition = calculatePointerPosition(mPointerAngle);
if (x >= (pointerPosition[0] - mPointerRadius * 2)
&& x <= (pointerPosition[0] + mPointerRadius * 2)
&& y >= (pointerPosition[1] - mPointerRadius * 2)
&& y <= (pointerPosition[1] + mPointerRadius * 2)) {
mIsMovingPointer = true;
return true;
}
float pointerLength = distanceRelative(x, y);//计算触摸点距离圆心的坐标:
//计算文本触摸区域的顶部距离圆心的坐标的距离:
//计算文本触摸区域的底部距离圆心的坐标的距离;
//计算触摸点的角度,
float tempPointerRectFAngle = (float) Math.toDegrees(Math.atan2(y, x));//文本触摸区域的的角度
if (pointerLength >= mMinRectRadius - mPointerRadius && pointerLength <= mMaxRectRadius + mPointerRadius) {
int willSelectedIndex = -1;
for (int i = 0; i < mTabRectItem.size(); i++) {
IndicatorTabRectItem item = mTabRectItem.get(i);
if (tempPointerRectFAngle >= item.getStartAngle() && tempPointerRectFAngle <= item.getEndAngle()) {
willSelectedIndex = i;
break;
}
}
if (mSelectTabIndex != willSelectedIndex) {
if (mTabChangeListener != null) {
mTabChangeListener.onTabSelected(this, willSelectedIndex);
}
}
setSelection(willSelectedIndex);
invalidate();
return true;
}
break;
case MotionEvent.ACTION_MOVE:
if (mIsMovingPointer) {
float tempPointerAngle = (float) Math.toDegrees(Math.atan2(y, x));
if (tempPointerAngle >= mTabItems.get(0).getStartAngle()
&& tempPointerAngle <= mTabItems.get(mTabItems.size() - 1).getEndAngle()) {
mPointerAngle = tempPointerAngle;
invalidate();
}
return true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mIsMovingPointer) {
mIsMovingPointer = false;
smoothMove();
return true;
}
break;
}
return false;
}
/**
* 根据触摸点距离圆心的距离,与每一块的活动角度,确定惟一的文本触摸块。
*/
// private void calculateTouchRect(float x, float y) {
//// =x-mWheelCenterX;
// float dpow2 = (float) (Math.pow((x - mWheelCenterX), 2) + Math.pow((y - mWheelCenterY), 2));
// float d = (float) Math.sqrt(dpow2);
// Log.d(TAG, "TouchRect---d=" + d);
// Log.d(TAG, "mMinRectRadius====" + mMinRectRadius);
// Log.d(TAG, "mMAXRectRadius====" + mMaxRectRadius);
//
//
// }
/**
* 相对与(x-mWheelCenterX,y-mWheelCenterY)坐标,
* 计算触摸点。求两点间距离,此时的圆心为
*/
public float distanceRelative(float x, float y) {
float dx = Math.abs(x);
float dy = Math.abs(y);
Log.d(TAG, "distance---d=" + (float) Math.hypot(dx, dy));
Log.d(TAG, "mMinRectRadius====" + mMinRectRadius);
Log.d(TAG, "mMAXRectRadius====" + mMaxRectRadius);
return (float) Math.hypot(dx, dy);
}
/**
* 计算触摸点。求两点间距离
*/
public float distance(float x, float y) {
float dx = Math.abs(x - mWheelCenterX);
float dy = Math.abs(y - mWheelCenterY);
Log.d(TAG, "distance---d=" + (float) Math.hypot(dx, dy));
Log.d(TAG, "mMinRectRadius====" + mMinRectRadius);
Log.d(TAG, "mMAXRectRadius====" + mMaxRectRadius);
return (float) Math.hypot(dx, dy);
}
private void smoothMove() {
int willSelectedIndex = -1;
for (int i = 0; i < mTabItems.size(); i++) {
IndicatorTabItem item = mTabItems.get(i);
if (mPointerAngle >= item.getStartAngle() && mPointerAngle <= item.getEndAngle()) {
willSelectedIndex = i;
break;
}
}
if (mSelectTabIndex != willSelectedIndex) {
if (mTabChangeListener != null) {
mTabChangeListener.onTabSelected(this, willSelectedIndex);
}
}
setSelection(willSelectedIndex);
}
public void setTabInfo(List<String> tabInfo) {
if (tabInfo == null && (tabInfo.size() == 0 && tabInfo.size() > 4)) {
return;
}
float totalPercent = 0.0f;
for (int i = 0; i < tabInfo.size(); i++) {
IndicatorTabItem item = new IndicatorTabItem();
item.setName(tabInfo.get(i));
item.setMesureWidth(mTabTextPaint.measureText(item.getName()));
Log.d(TAG, "--------setTabInfo()-------" + item.getName());
totalPercent += item.getMesureWidth();
mTabItems.add(item);
}
float startAngle = mStartAngle;
for (int i = 0; i < mTabItems.size(); i++) {
IndicatorTabItem tempItem = mTabItems.get(i);
float itemSweepAngle = mSweepAngle * tempItem.getMesureWidth() / totalPercent;
tempItem.setStartAngle(startAngle);
tempItem.setEndAngle(startAngle + itemSweepAngle);
startAngle += itemSweepAngle;
Log.d(TAG, "startAngle" + i + "======" + startAngle);
Log.d(TAG, "EndAngle" + i + "======" + (startAngle + itemSweepAngle));
}
setSelection(0);
initIndicatorTabRectItem(tabInfo);
}
private void initIndicatorTabRectItem(List<String> tabInfo) {
if (tabInfo == null && (tabInfo.size() == 0 && tabInfo.size() > 4)) {
return;
}
float totalPercent = 0.0f;
for (int i = 0; i < tabInfo.size(); i++) {
IndicatorTabRectItem rectItem = new IndicatorTabRectItem();
rectItem.setRectName(tabInfo.get(i));
rectItem.setRectWidth(mTabTextPaint.measureText(rectItem.getRectName()));
totalPercent += rectItem.getRectWidth();
Log.d(TAG, "----initIndicatorTabRectItem--------setRectWidth" + i + "======" + mTabTextPaint.measureText(rectItem.getRectName()));
mTabRectItem.add(rectItem);
}
float startAngle = mStartAngle;
for (int i = 0; i < mTabRectItem.size(); i++) {
IndicatorTabRectItem tempItem = mTabRectItem.get(i);
float itemSweepAngle = mSweepAngle * tempItem.getRectWidth() / totalPercent;
tempItem.setStartAngle(startAngle);
tempItem.setEndAngle(startAngle + itemSweepAngle);
startAngle += itemSweepAngle;
Log.d(TAG, "----initIndicatorTabRectItem-------startAngle" + i + "======" + startAngle);
Log.d(TAG, "----initIndicatorTabRectItem-------EndAngle" + i + "======" + (startAngle + itemSweepAngle));
}
}
/**
* 设置选择小点的每一段中心点角度。
*
* @param index
*/
public void setSelection(int index) {
if (index < 0 || index > mTabItems.size() - 1) {
return;
}
mSelectTabIndex = index;
mPointerAngle = (mTabItems.get(mSelectTabIndex).getStartAngle() + mTabItems.get(mSelectTabIndex).getEndAngle()) / 2;
invalidate();
}
public void setTabChangeListener(OnTabChangeListener tabChangeListener) {
this.mTabChangeListener = tabChangeListener;
}
public interface OnTabChangeListener {
void onTabSelected(View v, int position);
}
}
3)IndicatorTabRectItem
package com.example.chenkui.myapplication;
/**
* Created by chenkui on 2016/10/11.
*/
public class IndicatorTabRectItem {
private float startX;
private float endX;
private float startY;
private float endY;
private float rectWidth;
private float rectHeigth;
private String rectName;
private float startAngle;
private float endAngle;
public float getStartX() {
return startX;
}
public void setStartX(float startX) {
this.startX = startX;
}
public float getEndX() {
return endX;
}
public void setEndX(float endX) {
this.endX = endX;
}
public float getStartY() {
return startY;
}
public void setStartY(float startY) {
this.startY = startY;
}
public float getEndY() {
return endY;
}
public void setEndY(float endY) {
this.endY = endY;
}
public float getRectWidth() {
return rectWidth;
}
public void setRectWidth(float rectWidth) {
this.rectWidth = rectWidth;
}
public float getRectHeigth() {
return rectHeigth;
}
public void setRectHeigth(float rectHeigth) {
this.rectHeigth = rectHeigth;
}
public String getRectName() {
return rectName;
}
public void setRectName(String rectName) {
this.rectName = rectName;
}
public float getStartAngle() {
return startAngle;
}
public void setStartAngle(float startAngle) {
this.startAngle = startAngle;
}
public float getEndAngle() {
return endAngle;
}
public void setEndAngle(float endAngle) {
this.endAngle = endAngle;
}
}
4)IndicatorTabItem
package com.example.chenkui.myapplication;
public class IndicatorTabItem {
private String name;
private float mesureWidth;
private float startAngle;
private float endAngle;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getMesureWidth() {
return mesureWidth;
}
public void setMesureWidth(float mesureWidth) {
this.mesureWidth = mesureWidth;
}
public float getStartAngle() {
return startAngle;
}
public void setStartAngle(float startAngle) {
this.startAngle = startAngle;
}
public float getEndAngle() {
return endAngle;
}
public void setEndAngle(float endAngle) {
this.endAngle = endAngle;
}
}
5)activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="188dp"
android:orientation="vertical">
<com.example.chenkui.myapplication.IndicatorTabView
android:id="@+id/myView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<ImageView
android:id="@+id/titleTavIconImg"
android:layout_width="80dp"
android:layout_height="42dp"
android:layout_marginTop="15dp"
android:layout_centerHorizontal="true"
android:background="@drawable/title_icon" />
</RelativeLayout>
dimen.xml
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="tab_text">16dp</dimen>
<dimen name="tab_wheel_width">10dp</dimen>
<dimen name="tab_wheel_padding_bottom">130dp</dimen>
<dimen name="tab_wheel_padding_outer">30dp</dimen>
<dimen name="tab_wheel_padding_inner">20dp</dimen>
<dimen name="tab_pointer_radius">10dp</dimen>
</resources>
效果图如下:
时间: 2024-10-06 01:59:15