当前位置: 移动技术网 > 移动技术>移动开发>Android > Android实现仿网易新闻的顶部导航指示器

Android实现仿网易新闻的顶部导航指示器

2019年07月24日  | 移动技术网移动技术  | 我要评论

我们知道,页面导航器(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实现仿网易新闻的顶部导航指示器,希望对大家有所帮助

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网