Android实现仿网易新闻的顶部导航指示器
我们知道,页面导航器(navigator)在几乎所有的项目中都会用到,平时大多数时候为了节省时间,都会直接在github上面拿别人的开源项目来用,最近自己在复习自定义view,就尝试封装了一下,源码参考项目pagerslidingtabstrip
大家先来看一下效果图
基于文字的页面导航器
基于图片的页面导航器
使用方法
主要步骤分为三步
1)在xml文件里面
<com.xujun.viewpagertabindicator.tabpagerindicator android:id="@+id/pagerindicator" android:layout_width="match_parent" android:layout_height="50dp"/> <android.support.v4.view.viewpager android:layout_weight="1" android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="0dp"> </android.support.v4.view.viewpager>
2)在代码里面找到相应的控件
mpagerindicator = (tabpagerindicator) findviewbyid(r.id.pagerindicator); mviewpager = (viewpager) findviewbyid(r.id.viewpager);
3)初始化viewpager的adapter和将mviewpager和我们的mpagerindicator绑定
//必须先给viewpager设置适配器 mviewpager.setadapter(mpageradapter); //接着将mviewpage和我们的mpagerindicator绑定 mpagerindicator.setviewpager(mviewpager);
注意事项,
如果是文字标题导航的,我们只需重写在适配器里面重写getpagetitle这个方法
public charsequence getpagetitle(int position) { return titles[position]; }
如果是图标导航的,我们的适配器需要实现这个借口tabpagerindicator.icontabprovider,并重写里面的public int getpageiconresid(int position)这个方法
public class baseiconadapter extends fragmentpageradapter implements tabpagerindicator.icontabprovider { //省略了若干方法,有兴趣可以去看一下例子 @override public int getpageiconresid(int position) { return resids[position]; } }
我们可以通过setindicatormode(indicatormode indicatormode)这个方法设置不同的下滑线样式
mpagerindicator.setindicatormode(tabpagerindicator.indicatormode.mode_weight_expand_nosame, true);
mpagerindicator.setindicatormode(tabpagerindicator.indicatormode.mode_weight_expand_same, true);
关于下划线的 颜色,字体的颜色与大小的设置,请参照源码设置,这里就不列举了
大家先来看一下源码吧
public class tabpagerindicator extends horizontalscrollview { public interface icontabprovider { int getpageiconresid(int position); } // @formatter:off private static final int[] attrs = new int[]{ android.r.attr.textsize, android.r.attr.textcolor }; // @formatter:on private linearlayout.layoutparams wraptablayoutparams; private linearlayout.layoutparams expandedtablayoutparams; private final pagelistener pagelistener = new pagelistener(); public onpagechangelistener delegatepagelistener; private linearlayout tabscontainer; private viewpager pager; private int tabcount; private static final string tag = "xujun"; private int currentposition = 0; private float currentpositionoffset = 0f; private paint rectpaint; private paint dividerpaint; private int indicatorcolor = 0xff666666; private int underlinecolor = 0x1a000000; private int dividercolor = 0x1a000000; //表示是否扩展 private boolean isexpand = false; //表示下滑线的长度是否与标题字体的长度一样 private boolean issame = false; private boolean textallcaps = true; private int scrolloffset = 52; private int indicatorheight = 8; private int underlineheight = 2; private int dividerpadding = 12; //表示自己之间的间隔 private int horizontalpadding = 24; private int verticalpadding = 10; private int dividerwidth = 1; private int tabtextsize = 12; private int tabtextcolor = 0xff666666; private typeface tabtypeface = null; private int tabtypefacestyle = typeface.bold; private int lastscrollx = -1; private int tabbackgroundresid = r.drawable.background_tab; //indicator的样式 private indicatormode curmode = indicatormode.mode_wrap_expand_same; private locale locale; public tabpagerindicator(context context) { this(context, null); } public tabpagerindicator(context context, attributeset attrs) { this(context, attrs, 0); } public tabpagerindicator(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); setfillviewport(true); setwillnotdraw(false); tabscontainer = new linearlayout(context); tabscontainer.setorientation(linearlayout.horizontal); tabscontainer.setlayoutparams(new layoutparams(layoutparams.match_parent, layoutparams.match_parent)); addview(tabscontainer); //根据indicatormode初始化各个变量 setindicatormode(curmode); //初始化自定义属性 obtainattrs(context, attrs); rectpaint = new paint(); rectpaint.setantialias(true); rectpaint.setstyle(style.fill); dividerpaint = new paint(); dividerpaint.setantialias(true); dividerpaint.setstrokewidth(dividerwidth); wraptablayoutparams = new linearlayout.layoutparams(layoutparams.wrap_content, layoutparams.match_parent); expandedtablayoutparams = new linearlayout.layoutparams(0, layoutparams.match_parent, 1.0f); if (locale == null) { locale = getresources().getconfiguration().locale; } } public void setindicatormode(indicatormode indicatormode) { this.setindicatormode(indicatormode, false); } public void setindicatormode(indicatormode indicatormode, boolean isnotify) { switch (indicatormode) { case mode_wrap_expand_same: isexpand = false; issame = true; break; case mode_wrap_expand_nosame: isexpand = false; issame = false; break; case mode_weight_expand_nosame: isexpand = true; issame = false; break; case mode_weight_expand_same: isexpand = true; issame = true; break; } this.curmode = indicatormode; if (isnotify) { notifydatasetchanged(); } } private void obtainattrs(context context, attributeset attrs) { displaymetrics dm = getresources().getdisplaymetrics(); scrolloffset = (int) typedvalue.applydimension(typedvalue.complex_unit_dip, scrolloffset, dm); indicatorheight = (int) typedvalue.applydimension(typedvalue.complex_unit_dip, indicatorheight, dm); underlineheight = (int) typedvalue.applydimension(typedvalue.complex_unit_dip, underlineheight, dm); dividerpadding = (int) typedvalue.applydimension(typedvalue.complex_unit_dip, dividerpadding, dm); horizontalpadding = (int) typedvalue.applydimension(typedvalue.complex_unit_dip, horizontalpadding, dm); dividerwidth = (int) typedvalue.applydimension(typedvalue.complex_unit_dip, dividerwidth, dm); tabtextsize = (int) typedvalue.applydimension(typedvalue.complex_unit_sp, tabtextsize, dm); // get system attrs (android:textsize and android:textcolor) typedarray a = context.obtainstyledattributes(attrs, attrs); tabtextsize = a.getdimensionpixelsize(0, tabtextsize); tabtextcolor = a.getcolor(1, tabtextcolor); a.recycle(); // get custom attrs a = context.obtainstyledattributes(attrs, r.styleable.tabpagerindicator); indicatorcolor = a.getcolor(r.styleable.tabpagerindicator_pstsindicatorcolor, indicatorcolor); underlinecolor = a.getcolor(r.styleable.tabpagerindicator_pstsunderlinecolor, underlinecolor); dividercolor = a.getcolor(r.styleable.tabpagerindicator_pstsdividercolor, dividercolor); indicatorheight = a.getdimensionpixelsize(r.styleable .tabpagerindicator_pstsindicatorheight, indicatorheight); underlineheight = a.getdimensionpixelsize(r.styleable .tabpagerindicator_pstsunderlineheight, underlineheight); dividerpadding = a.getdimensionpixelsize(r.styleable .tabpagerindicator_pstsdividerpadding, dividerpadding); horizontalpadding = a.getdimensionpixelsize(r.styleable .tabpagerindicator_pststabpaddingleftright, horizontalpadding); tabbackgroundresid = a.getresourceid(r.styleable.tabpagerindicator_pststabbackground, tabbackgroundresid); isexpand = a.getboolean(r.styleable.tabpagerindicator_pstsshouldexpand, isexpand); scrolloffset = a.getdimensionpixelsize(r.styleable.tabpagerindicator_pstsscrolloffset, scrolloffset); textallcaps = a.getboolean(r.styleable.tabpagerindicator_pststextallcaps, textallcaps); a.recycle(); } public void setviewpager(viewpager pager) { this.pager = pager; if (pager.getadapter() == null) { throw new illegalstateexception("viewpager does not have adapter instance."); } pager.addonpagechangelistener(pagelistener); notifydatasetchanged(); } public void addonpagechangelistener(onpagechangelistener listener) { this.delegatepagelistener = listener; } public void notifydatasetchanged() { //先移除掉所有的view ,防止重复添加 tabscontainer.removeallviews(); tabcount = pager.getadapter().getcount(); for (int i = 0; i < tabcount; i++) { //区分是文字还是icon的导航 if (pager.getadapter() instanceof icontabprovider) { addicontab(i, ((icontabprovider) pager.getadapter()).getpageiconresid(i)); } else { addtexttab(i, pager.getadapter().getpagetitle(i).tostring()); } } updatetabstyles(); //监听视图树,在绘制完毕后调用相关的方法完成初始化工作 getviewtreeobserver().addongloballayoutlistener(new ongloballayoutlistener() { @suppresswarnings("deprecation") @suppresslint("newapi") @override public void ongloballayout() { if (build.version.sdk_int < build.version_codes.jelly_bean) { getviewtreeobserver().removeglobalonlayoutlistener(this); } else { getviewtreeobserver().removeongloballayoutlistener(this); } currentposition = pager.getcurrentitem(); scrolltochild(currentposition, 0); } }); } private void addtexttab(final int position, string title) { textview tab = new textview(getcontext()); tab.settext(title); tab.setgravity(gravity.center); tab.setsingleline(); addtab(position, tab); } private void addicontab(final int position, int resid) { imagebutton tab = new imagebutton(getcontext()); tab.setimageresource(resid); addtab(position, tab); } //添加孩子 private void addtab(final int position, view tab) { tab.setfocusable(true); // 设置监听 tab.setonclicklistener(new onclicklistener() { @override public void onclick(view v) { pager.setcurrentitem(position); } }); // 这里我们下划线的 高度是否与文字的长度保持一致,是通过给孩子设置padding或者margin实现的 // 注意与ondraw里面的逻辑结合起来 if (!issame) { tab.setpadding(horizontalpadding, verticalpadding, horizontalpadding, verticalpadding); wraptablayoutparams.setmargins(0, 0, 0, 0); expandedtablayoutparams.setmargins(0, 0, 0, 0); } else { wraptablayoutparams.setmargins(horizontalpadding, verticalpadding, horizontalpadding, verticalpadding); expandedtablayoutparams.setmargins(horizontalpadding, verticalpadding, horizontalpadding, verticalpadding); } //根据是否可以扩展来设置不同的layoutparams tabscontainer.addview(tab, position, isexpand ? expandedtablayoutparams : wraptablayoutparams); } private void updatetabstyles() { for (int i = 0; i < tabcount; i++) { view v = tabscontainer.getchildat(i); v.setbackgroundresource(tabbackgroundresid); if (v instanceof textview) { textview tab = (textview) v; tab.settextsize(typedvalue.complex_unit_px, tabtextsize); tab.settypeface(tabtypeface, tabtypefacestyle); tab.settextcolor(tabtextcolor); // setallcaps() is only available from api 14, so the upper case is made manually // if we are on a pre-ics-build if (textallcaps) { if (build.version.sdk_int >= build.version_codes.ice_cream_sandwich) { tab.setallcaps(true); } else { tab.settext(tab.gettext().tostring().touppercase(locale)); } } } } } // 调用这个方法是horizontalscrollview滑动到相应的位置 private void scrolltochild(int position, int offset) { if (tabcount == 0) { return; } int newscrollx; view child = tabscontainer.getchildat(position); int left = child.getleft(); if (issame) { newscrollx = left + offset - horizontalpadding; } else { newscrollx = left + offset; } if (position > 0 || offset > 0) { newscrollx -= scrolloffset; } log.i(tag, "scrolltochild:newscrollx=" + newscrollx); if (newscrollx != lastscrollx) { lastscrollx = newscrollx; scrollto(newscrollx, 0); } } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); if (isineditmode() || tabcount == 0) { return; } final int height = getheight(); // draw indicator line rectpaint.setcolor(indicatorcolor); // default: line below current tab view currenttab = tabscontainer.getchildat(currentposition); float lineleft = currenttab.getleft(); float lineright = currenttab.getright(); // if there is an offset, start interpolating left and right coordinates between current // and next tab if (currentpositionoffset > 0f && currentposition < tabcount - 1) { view nexttab = tabscontainer.getchildat(currentposition + 1); final float nexttableft = nexttab.getleft(); final float nexttabright = nexttab.getright(); lineleft = (currentpositionoffset * nexttableft + (1f - currentpositionoffset) * lineleft); lineright = (currentpositionoffset * nexttabright + (1f - currentpositionoffset) * lineright); } canvas.drawrect(lineleft, height - indicatorheight, lineright, height, rectpaint); // draw underline rectpaint.setcolor(underlinecolor); canvas.drawrect(0, height - underlineheight, tabscontainer.getwidth(), height, rectpaint); // draw divider dividerpaint.setcolor(dividercolor); for (int i = 0; i < tabcount - 1; i++) { view tab = tabscontainer.getchildat(i); if (!issame) { canvas.drawline(tab.getright(), dividerpadding, tab.getright(), height - dividerpadding, dividerpaint); } else { canvas.drawline(tab.getright() + horizontalpadding, dividerpadding, tab.getright() + horizontalpadding, height - dividerpadding, dividerpaint); } } } private class pagelistener implements onpagechangelistener { @override public void onpagescrolled(int position, float positionoffset, int positionoffsetpixels) { currentposition = position; currentpositionoffset = positionoffset; view child = tabscontainer.getchildat(position); int width = child.getwidth(); if (issame) { width += horizontalpadding * 2; } log.i(tag, "onpagescrolled:width=" + width); // 调用这个方法是horizontalscrollview滑动到相应的位置 scrolltochild(position, (int) (positionoffset * width)); //调用这个方法重新绘制 invalidate(); if (delegatepagelistener != null) { delegatepagelistener.onpagescrolled(position, positionoffset, positionoffsetpixels); } } @override public void onpagescrollstatechanged(int state) { if (delegatepagelistener != null) { delegatepagelistener.onpagescrollstatechanged(state); } } @override public void onpageselected(int position) { if (delegatepagelistener != null) { delegatepagelistener.onpageselected(position); } } } public void setindicatorcolor(int indicatorcolor) { this.indicatorcolor = indicatorcolor; invalidate(); } public void setindicatorcolorresource(int resid) { this.indicatorcolor = getresources().getcolor(resid); invalidate(); } public int getindicatorcolor() { return this.indicatorcolor; } public void setindicatorheight(int indicatorlineheightpx) { this.indicatorheight = indicatorlineheightpx; invalidate(); } public int getindicatorheight() { return indicatorheight; } public void setunderlinecolor(int underlinecolor) { this.underlinecolor = underlinecolor; invalidate(); } public void setunderlinecolorresource(int resid) { this.underlinecolor = getresources().getcolor(resid); invalidate(); } public int getunderlinecolor() { return underlinecolor; } public void setdividercolor(int dividercolor) { this.dividercolor = dividercolor; invalidate(); } public void setdividercolorresource(int resid) { this.dividercolor = getresources().getcolor(resid); invalidate(); } public int getdividercolor() { return dividercolor; } public void setunderlineheight(int underlineheightpx) { this.underlineheight = underlineheightpx; invalidate(); } public int getunderlineheight() { return underlineheight; } public void setdividerpadding(int dividerpaddingpx) { this.dividerpadding = dividerpaddingpx; invalidate(); } public int getdividerpadding() { return dividerpadding; } public void setscrolloffset(int scrolloffsetpx) { this.scrolloffset = scrolloffsetpx; invalidate(); } public int getscrolloffset() { return scrolloffset; } public void setexpand(boolean expand) { this.isexpand = expand; requestlayout(); } public boolean getexpand() { return isexpand; } public boolean istextallcaps() { return textallcaps; } public void setallcaps(boolean textallcaps) { this.textallcaps = textallcaps; } public void settextsize(int textsizepx) { this.tabtextsize = textsizepx; updatetabstyles(); } public int gettextsize() { return tabtextsize; } public void settextcolor(int textcolor) { this.tabtextcolor = textcolor; updatetabstyles(); } public void settextcolorresource(int resid) { this.tabtextcolor = getresources().getcolor(resid); updatetabstyles(); } public int gettextcolor() { return tabtextcolor; } public void settypeface(typeface typeface, int style) { this.tabtypeface = typeface; this.tabtypefacestyle = style; updatetabstyles(); } public void settabbackground(int resid) { this.tabbackgroundresid = resid; } public int gettabbackground() { return tabbackgroundresid; } public void settabpaddingleftright(int paddingpx) { this.horizontalpadding = paddingpx; updatetabstyles(); } public int gettabpaddingleftright() { return horizontalpadding; } @override public void onrestoreinstancestate(parcelable state) { savedstate savedstate = (savedstate) state; super.onrestoreinstancestate(savedstate.getsuperstate()); currentposition = savedstate.currentposition; requestlayout(); } @override public parcelable onsaveinstancestate() { parcelable superstate = super.onsaveinstancestate(); savedstate savedstate = new savedstate(superstate); savedstate.currentposition = currentposition; return savedstate; } //用来保存状态 static class savedstate extends basesavedstate { int currentposition; public savedstate(parcelable superstate) { super(superstate); } private savedstate(parcel in) { super(in); currentposition = in.readint(); } @override public void writetoparcel(parcel dest, int flags) { super.writetoparcel(dest, flags); dest.writeint(currentposition); } public static final creator<savedstate> creator = new creator<savedstate>() { @override public savedstate createfromparcel(parcel in) { return new savedstate(in); } @override public savedstate[] newarray(int size) { return new savedstate[size]; } }; } /** * 定义4种模式 */ public enum indicatormode { // 给枚举传入自定义的int值 mode_wrap_expand_same(1),// 可扩展,导航线跟标题相等 mode_wrap_expand_nosame(2),// 可扩展,导标不相等 mode_weight_expand_same(3),// 可扩展,导航线跟标题相等 mode_weight_expand_nosame(4);// 可扩展,导标不相等 private int value; indicatormode(int value) { this.value = value; } public int getvalue() { return value; } } }
思路主要 可以分为以下几个步骤
1)在构造方法里面初始化各种工作,包括一些自定义属性,画笔等等
public tabpagerindicator(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); //初始化各种工作 //根据indicatormode初始化各个变量 setindicatormode(curmode); //初始化自定义属性 obtainattrs(context, attrs); rectpaint = new paint(); rectpaint.setantialias(true); rectpaint.setstyle(style.fill); dividerpaint = new paint(); dividerpaint.setantialias(true); dividerpaint.setstrokewidth(dividerwidth); if (locale == null) { locale = getresources().getconfiguration().locale; } }
2)通过setviewpager()这个方法将控件与viewpager联系起来
public void setviewpager(viewpager pager) { this.pager = pager; if (pager.getadapter() == null) { throw new illegalstateexception("viewpager does not have adapter instance."); } pager.addonpagechangelistener(pagelistener); notifydatasetchanged(); } public void notifydatasetchanged() { //先移除掉所有的view ,防止重复添加 tabscontainer.removeallviews(); tabcount = pager.getadapter().getcount(); for (int i = 0; i < tabcount; i++) { //区分是文字还是icon的导航 if (pager.getadapter() instanceof icontabprovider) { addicontab(i, ((icontabprovider) pager.getadapter()).getpageiconresid(i)); } else { addtexttab(i, pager.getadapter().getpagetitle(i).tostring()); } } updatetabstyles(); //监听视图树,在绘制完毕后调用相关的方法完成初始化工作 getviewtreeobserver().addongloballayoutlistener(new ongloballayoutlistener() { @suppresswarnings("deprecation") @suppresslint("newapi") @override public void ongloballayout() { if (build.version.sdk_int < build.version_codes.jelly_bean) { getviewtreeobserver().removeglobalonlayoutlistener(this); } else { getviewtreeobserver().removeongloballayoutlistener(this); } currentposition = pager.getcurrentitem(); scrolltochild(currentposition, 0); } }); }
3)在 ondraw里面根据不同的 mode绘制不同的下划线样式
@override protected void ondraw(canvas canvas) { super.ondraw(canvas); if (isineditmode() || tabcount == 0) { return; } final int height = getheight(); // draw indicator line rectpaint.setcolor(indicatorcolor); // default: line below current tab view currenttab = tabscontainer.getchildat(currentposition); float lineleft = currenttab.getleft(); float lineright = currenttab.getright(); // if there is an offset, start interpolating left and right coordinates between current // and next tab if (currentpositionoffset > 0f && currentposition < tabcount - 1) { view nexttab = tabscontainer.getchildat(currentposition + 1); final float nexttableft = nexttab.getleft(); final float nexttabright = nexttab.getright(); lineleft = (currentpositionoffset * nexttableft + (1f - currentpositionoffset) * lineleft); lineright = (currentpositionoffset * nexttabright + (1f - currentpositionoffset) * lineright); } canvas.drawrect(lineleft, height - indicatorheight, lineright, height, rectpaint); // draw underline rectpaint.setcolor(underlinecolor); canvas.drawrect(0, height - underlineheight, tabscontainer.getwidth(), height, rectpaint); // draw divider dividerpaint.setcolor(dividercolor); for (int i = 0; i < tabcount - 1; i++) { view tab = tabscontainer.getchildat(i); if (!issame) { canvas.drawline(tab.getright(), dividerpadding, tab.getright(), height - dividerpadding, dividerpaint); } else { canvas.drawline(tab.getright() + horizontalpadding, dividerpadding, tab.getright() + horizontalpadding, height - dividerpadding, dividerpaint); } } }
4)在viewpager滑动的时候,会调用相应的方法来刷新界面,因为前面我们在setviewpager的时候为其添加pagelistener监听器
public void setviewpager(viewpager pager) { //省略了若干方法 pager.addonpagechangelistener(pagelistener); } private class pagelistener implements onpagechangelistener { @override public void onpagescrolled(int position, float positionoffset, int positionoffsetpixels) { currentposition = position; currentpositionoffset = positionoffset; view child = tabscontainer.getchildat(position); int width = child.getwidth(); if (issame) { width += horizontalpadding * 2; } log.i(tag, "onpagescrolled:width=" + width); // 调用这个方法是horizontalscrollview滑动到相应的位置 scrolltochild(position, (int) (positionoffset * width)); //调用这个方法重新绘制 invalidate(); if (delegatepagelistener != null) { delegatepagelistener.onpagescrolled(position, positionoffset, positionoffsetpixels); } } @override public void onpagescrollstatechanged(int state) { if (delegatepagelistener != null) { delegatepagelistener.onpagescrollstatechanged(state); } } @override public void onpageselected(int position) { if (delegatepagelistener != null) { delegatepagelistener.onpageselected(position); } } }
以上所述是小编给大家介绍的android实现仿网易新闻的顶部导航指示器,希望对大家有所帮助
上一篇: Android 判断真机和模拟器的方法
下一篇: 从JVM分析Java的类的加载和卸载机制