欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-06 18:45:02
我们知道,页面导航器(navigator)在几乎所有的项目中都会用到,平时大多数时候为了节省时间,都会直接在github上面拿别人的开源项目来用,最近自己在复习自定义vie...

我们知道,页面导航器(navigator)在几乎所有的项目中都会用到,平时大多数时候为了节省时间,都会直接在github上面拿别人的开源项目来用,最近自己在复习自定义view,就尝试封装了一下,源码参考项目pagerslidingtabstrip

大家先来看一下效果图

基于文字的页面导航器

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

基于图片的页面导航器

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

使用方法

主要步骤分为三步

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);

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

mpagerindicator.setindicatormode(tabpagerindicator.indicatormode.mode_weight_expand_same,
true);

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

关于下划线的 颜色,字体的颜色与大小的设置,请参照源码设置,这里就不列举了

大家先来看一下源码吧

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