Android自定义控件实现可左右滑动的导航条
GPT4.0+Midjourney绘画+国内大模型 会员永久免费使用!
【 如果你想靠AI翻身,你先需要一个靠谱的工具! 】
本文实例为大家分享了Android实现可左右滑动导航条的具体代码,供大家参考,具体内容如下
先上效果图:
这个控件其实算是比较轻量级的,相信不少小伙伴都能做出来。因为项目中遇到了一些特殊的定制要求,所以就自己写了一个,这里放出来。
首先来分析下这个控件的功能:
•能够响应左右滑动,并且能响应快速滑动
•选择项和未选择项有不同的样式表现,比如前景色,背景色,字体大小变粗之内的
•在切换选项的时候,如果当前选项未完全呈现在界面前,则自动滚动直至当前选项完全暴露显示
前两条还有,简简单单就实现了,主要是第三点,这才是我自定义这个控件的原因!那么如果要实现这个控件,需要用到哪些知识呢?
•用Scroller来实现控件的滚动
•用VelocityTracker来实现控件的快速滚动
如果上面两种技术你都已经会了,那么我们就可以开始讲解代码了。首先是一些属性的Getter/Setter方法,这里采用的链式设置法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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类:
1 2 3 4 5 6 7 8 9 10 11 | 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选项的初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | @Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec){ width = MeasureSpec.getSize(widthMeasureSpec); height = MeasureSpec.getSize(heightMeasureSpec); //初始化Item initItems(); 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中处理滚动超屏时的恢复操作,以及点击的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | @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选项被选中时自动滚动至完全显示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | @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); } } |
接下来就是怎么使用这个控件了,布局文件:
1 2 3 4 5 6 7 8 9 10 11 | <? 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.IndicatorView android:id = "@+id/indicator" android:layout_width = "match_parent" android:layout_height = "38dp" /> </ RelativeLayout > |
MainActvity中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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(); } } |
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
微信公众号搜索 “ 脚本之家 ” ,选择关注
程序猿的那些事、送书等活动等着你
相关文章
flutter开发技巧自定页面指示器PageIndicator详解
这篇文章主要为大家介绍了flutter开发技巧自定页面指示器PageIndicator详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-01-01Android实现Activities之间进行数据传递的方法
这篇文章主要介绍了Android实现Activities之间进行数据传递的方法,涉及Android中Activities的使用技巧,需要的朋友可以参考下2015-04-04
最新评论