
这个控件其实算是比较轻量级的,相信不少小伙伴都能做出来。因为项目中遇到了一些特殊的定制要求,所以就自己写了一个,这里放出来。
首先来分析下这个控件的功能:
•能够响应左右滑动,并且能响应快速滑动
•选择项和未选择项有不同的样式表现,比如前景色,背景色,字体大小变粗之内的
•在切换选项的时候,如果当前选项未完全呈现在界面前,则自动滚动直至当前选项完全暴露显示
前两条还有,简简单单就实现了,主要是第三点,这才是我自定义这个控件的原因!那么如果要实现这个控件,需要用到哪些知识呢?
•用Scroller来实现控件的滚动
•用VelocityTracker来实现控件的快速滚动
如果上面两种技术你都已经会了,那么我们就可以开始讲解代码了。首先是一些属性的Getter/Setter方法,这里采用的链式设置法:
public IndicatorView color(int colorDefault, int colorSelected, int colorBg){this.colorDefault = colorDefault;this.colorSelected = colorSelected;this.colorBg = colorBg;return this; } public IndicatorView textSize(int textSize){this.textSize = textSize;return this; } public IndicatorView text(String[] texts){this.texts = texts;return this; } public IndicatorView padding(int[] padding){this.padding = padding;return this; } public IndicatorView defaultSelect(int defaultSelect){this.selectItem = defaultSelect;return this; } public IndicatorView lineHeight(int lineHeight){this.lineHeight = lineHeight;return this; } public IndicatorView listener(OnIndicatorChangedListener listener){this.listener = listener;return this; } public IndicatorView type(Type type){this.type = type;return this; }这里我们将每一个选项抽象成了一个Item类: public class Item {String text;int colorDefault;int colorSelected;int textSize;boolean isSelected = false;int width;Point drawPoint;int[] padding = new int[4];Rect rect = new Rect(); } 然后是控件的初始化操作,主要根据当前控件的宽高,以及设置的一些属性,进行Item选项的初始化: @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){width = MeasureSpec.getSize(widthMeasureSpec);height = MeasureSpec.getSize(heightMeasureSpec);//初始化IteminitItems();super.onMeasure(widthMeasureSpec, heightMeasureSpec); } private void initItems(){items.clear();measureWidth = 0;for(int i = 0; i < texts.length; i++){ Item item = new Item(); item.text = texts[i]; item.colorDefault = colorDefault; item.colorSelected = colorSelected; item.textSize = textSize; for(int j = 0; j < item.padding.length; j++){item.padding[j] = padding[j]; } mPaint.setTextSize(item.textSize); item.width = (int)mPaint.measureText(item.text); int dx = 0; if(i - 1 < 0){dx = 0; }else{for(int j = 0; j < i; j++){ dx += items.get(j).padding[0] + items.get(j).width + items.get(j).padding[2];} } int startX = item.padding[0] + dx; Paint.FontMetrics metrics = mPaint.getFontMetrics(); int startY = (int)(height / 2 + (metrics.bottom - metrics.top) / 2 - metrics.bottom); item.drawPoint = new Point(startX, startY); //设置区域 item.rect.left = item.drawPoint.x - item.padding[0]; item.rect.top = 0; item.rect.right = item.drawPoint.x + item.width + item.padding[2]; item.rect.bottom = height; //设置默认 if(i == selectItem){item.isSelected = true; } measureWidth += item.rect.width(); items.add(item);}//重绘invalidate(); } 接下来是事件处理,逻辑很简单。在DOWN时间记录坐标值,在MOVE中处理控件的滚动,在UP中处理滚动超屏时的恢复操作,以及点击的操作。 @Override public boolean onTouchEvent(MotionEvent event){if(mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);switch(event.getAction()){ case MotionEvent.ACTION_DOWN:mTouchX = (int)event.getX();mTouchY = (int)event.getY();mMoveX = mTouchX;return true; case MotionEvent.ACTION_MOVE:if(measureWidth > width){ int dx = (int)event.getX() - mMoveX; if(dx > 0){ // 右滑if(mScroller.getFinalX() > 0){ mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), -dx, 0);}else{ mScroller.setFinalX(0);} }else{ //左滑if(mScroller.getFinalX() + width - dx < measureWidth){ mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), -dx, 0);}else{ mScroller.setFinalX(measureWidth - width);} } mMoveX = (int)event.getX(); invalidate();}break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL:if(measureWidth > width){ mVelocityTracker.computeCurrentVelocity(1000); int max = Math.max(Math.abs(mScroller.getCurrX()), Math.abs(measureWidth - width - mScroller.getCurrX())); mScroller.fling(mScroller.getFinalX(), mScroller.getFinalY(), (int)-mVelocityTracker.getXVelocity(), (int)-mVelocityTracker.getYVelocity(), 0, max, mScroller.getFinalY(), mScroller.getFinalY()); //手指抬起时,根据滚动偏移量初始化位置 if(mScroller.getCurrX() < 0){mScroller.abortAnimation();mScroller.startScroll(mScroller.getCurrX(), mScroller.getCurrY(), -mScroller.getCurrX(), 0); }else if(mScroller.getCurrX() + width > measureWidth){mScroller.abortAnimation();mScroller.startScroll(mScroller.getCurrX(), mScroller.getCurrY(), measureWidth - width - mScroller.getCurrX(), 0); }}if(event.getAction() == MotionEvent.ACTION_UP){ int mUpX = (int)event.getX(); int mUpY = (int)event.getY(); //模拟点击操作 if(Math.abs(mUpX - mTouchX) <= mTouchSlop && Math.abs(mUpY - mTouchY) <= mTouchSlop){for(int i = 0; i < items.size(); i++){ if(items.get(i).rect.contains(mScroller.getCurrX() + mUpX, getScrollY() + mUpY)){setSelected(i);return super.onTouchEvent(event); }} }}break; default:break;}return super.onTouchEvent(event); } 接下来就是很重要的一段代码,因为这段代码,才可以让未完全显示的Item选项被选中时自动滚动至完全显示: public void setSelected(int position){if(position >= items.size()){ return;}for(int i = 0; i < items.size(); i++){ if(i == position){items.get(i).isSelected = true;if(i != selectItem){ selectItem = i; //判断是否需要滑动到完全可见 if(mScroller.getCurrX() + width < items.get(i).rect.right){mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), items.get(i).rect.right - mScroller.getCurrX() - width, mScroller.getFinalY()); } if(items.get(i).rect.left < mScroller.getCurrX()){mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), items.get(i).rect.left - mScroller.getCurrX(), mScroller.getFinalY()); } if(listener != null){listener.onChanged(selectItem); }} }else{items.get(i).isSelected = false; }}invalidate(); } 然后就是绘制方法了,相当于完全代理给了Item来实现: @Override protected void onDraw(Canvas canvas){mPaint.setAntiAlias(true);canvas.drawColor(colorBg);for(Item item : items){ mPaint.setTextSize(item.textSize); if(item.isSelected){if(type == Type.SelectByLine){ //绘制红线 mPaint.setColor(item.colorSelected); mPaint.setStyle(Paint.Style.FILL); canvas.drawRoundRect(new RectF(item.rect.left, item.rect.bottom - lineHeight, item.rect.right, item.rect.bottom), 3, 3, mPaint);}else if(type == Type.SelectByFill){ //绘制红色背景 mPaint.setColor(getContext().getResources().getColor(android.R.color.holo_red_light)); mPaint.setStyle(Paint.Style.FILL); canvas.drawRoundRect(new RectF(item.rect.left + 6, item.rect.top, item.rect.right - 6, item.rect.bottom), item.rect.height() * 5 / 12, item.rect.height() * 5 / 12, mPaint);}mPaint.setColor(item.colorSelected); }else{mPaint.setColor(item.colorDefault); } canvas.drawText(item.text, item.drawPoint.x, item.drawPoint.y, mPaint);} }接下来就是怎么使用这个控件了,布局文件:<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent"> <cc.wxf.androiddemo.indicator.IndicatorViewandroid:id="@+id/indicator"android:layout_width="match_parent"android:layout_height="38dp" /></RelativeLayout>MainActvity中:
package cc.wxf.androiddemo;import android.content.Context;import android.content.res.Resources;import android.os.Bundle;import android.support.v4.app.FragmentActivity;import cc.wxf.androiddemo.indicator.IndicatorView;public class MainActivity extends FragmentActivity { private IndicatorView indicatorView; @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initIndicator(); } private void initIndicator(){indicatorView = (IndicatorView)findViewById(R.id.indicator);Resources resources = getResources();indicatorView.color(resources.getColor(android.R.color.black),resources.getColor(android.R.color.holo_red_light),resources.getColor(android.R.color.darker_gray)).textSize(sp2px(this, 16)).padding(new int[]{dip2px(this, 14), dip2px(this, 14), dip2px(this, 14), dip2px(this, 14)}).text(new String[]{"电视剧","电影","综艺","片花","动漫","娱乐","会员1","会员2","会员3","会员4","会员5","会员6"}).defaultSelect(0).lineHeight(dip2px(this, 3)).listener(new IndicatorView.OnIndicatorChangedListener(){ @Override public void onChanged(int position){ }}).commit(); } public static int dip2px(Context context, float dipValue){final float scale = context.getResources().getDisplayMetrics().density;return (int)(dipValue * scale + 0.5f); } public static int sp2px(Context context, float spValue){final float scale = context.getResources().getDisplayMetrics().scaledDensity;return (int)(spValue * scale + 0.5f); } @Override protected void onDestroy() {super.onDestroy();indicatorView.release(); }}以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。