android 自定义View弯曲滑竿指示器

枕上从妨一夜睡,灯前读尽十年诗。这篇文章主要讲述android 自定义View弯曲滑竿指示器相关的知识,希望能为你提供帮助。
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; privateString 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>

【android 自定义View弯曲滑竿指示器】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>

效果图如下:
android 自定义View弯曲滑竿指示器

文章图片

github源码链接地址:
https://github.com/Andrewcenquck/MyApplication

    推荐阅读