Android自定义控件实现可左右滑动的导航条
程序员文章站
2024-03-04 17:15:23
先上效果图:
这个控件其实算是比较轻量级的,相信不少小伙伴都能做出来。因为项目中遇到了一些特殊的定制要求,所以就自己写了一个,这里放出来。
首先来分析下...
先上效果图:
这个控件其实算是比较轻量级的,相信不少小伙伴都能做出来。因为项目中遇到了一些特殊的定制要求,所以就自己写了一个,这里放出来。
首先来分析下这个控件的功能:
•能够响应左右滑动,并且能响应快速滑动
•选择项和未选择项有不同的样式表现,比如前景色,背景色,字体大小变粗之内的
•在切换选项的时候,如果当前选项未完全呈现在界面前,则自动滚动直至当前选项完全暴露显示
前两条还有,简简单单就实现了,主要是第三点,这才是我自定义这个控件的原因!那么如果要实现这个控件,需要用到哪些知识呢?
•用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); //初始化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中处理滚动超屏时的恢复操作,以及点击的操作。
@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.indicatorview android: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(); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。