Android多个TAB选项卡切换效果
在前一期中,我们做了悬浮头部的两个tab切换和下拉刷新效果,后来项目中要求改成三个tab,当时就能估量了一下,如果从之前的改,也不是不可以,但是要互相记住的状态就太多了,很容易出现错误。就决定重新实现一下这个效果,为此先写了一个demo,这期间项目都已经又更新了两个版本了。demo还木有变成文章。
之前的版本中是采用了一个可以下拉刷新的listview,之后在listview中添加了两个头部,并且在该布局上的上面用了一个一模一样的切换tab,如果没有看过前面版本的,可以看看前一个版本,listview多tab上滑悬浮。
基于上述思路我们先来看看页面布局:main_activity
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/color_gray_eaeaea" > <android.support.v4.view.viewpager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" /> <linearlayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/white" android:orientation="vertical" > <imageview android:id="@+id/show_event_detail_bg" android:layout_width="fill_parent" android:layout_height="135dip" android:contentdescription="@string/empty" android:scaletype="fitxy" android:src="@drawable/header_default_bk" /> <textview android:id="@+id/show_event_detail_desc" android:layout_width="wrap_content" android:layout_height="104dip" android:paddingbottom="24dip" android:layout_marginleft="15dip" android:layout_marginright="15dip" android:paddingtop="25dip" android:text="@string/head_title_desc" android:textcolor="@color/color_black_333333" android:textsize="14sp" /> <view style="@style/horizontal_gray_divider" /> <view style="@style/horizontal_gray_divider" /> <com.example.refreashtabview.sliding.pagerslidingtabstrip android:id="@+id/show_tabs" android:layout_width="match_parent" android:layout_height="44dip" android:background="@color/white" /> </linearlayout> </relativelayout>
页面采用了两层,后面一层为viewpager,前面为悬浮头与tab切换,在这大家应该都想到了会怎么样实现,viewpager中添加已经fragment,每个fragment里面加入一个可下拉刷新的listview,根据listview的滑动来控制前一帧页面的位置。
来看看页面代码吧,mainacitivity.java
public class mainactivity extends actionbaractivity implements onpagechangelistener, scrolltabholder { private pagerslidingtabstrip tabs; private viewpager viewpager; private slidingpageradapter adapter; private linearlayout header; private int headerheight; private int headertranslationdis; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main_activity); getheaderheight(); findviews(); setuppager(); setuptabs(); } private void findviews() { tabs = (pagerslidingtabstrip) findviewbyid(r.id.show_tabs); viewpager = (viewpager) findviewbyid(r.id.pager); header = (linearlayout) findviewbyid(r.id.header); } private void getheaderheight() { headerheight = getresources().getdimensionpixelsize(r.dimen.max_header_height); headertranslationdis = -getresources().getdimensionpixelsize(r.dimen.header_offset_dis); } private void setuppager() { adapter = new slidingpageradapter(getsupportfragmentmanager(), this, viewpager); adapter.settabholderscrollinglistener(this);//控制页面上滑 viewpager.setoffscreenpagelimit(adapter.getcachecount()); viewpager.setadapter(adapter); viewpager.setonpagechangelistener(this); } private void setuptabs() { tabs.setshouldexpand(true); tabs.setindicatorcolorresource(r.color.color_purple_bd6aff); tabs.setunderlinecolorresource(r.color.color_purple_bd6aff); tabs.setcheckedtextcolorresource(r.color.color_purple_bd6aff); tabs.setviewpager(viewpager); } @override public void onpagescrolled(int position, float positionoffset, int positionoffsetpixels) { tabs.onpagescrolled(position, positionoffset, positionoffsetpixels); } @override public void onpageselected(int position) { tabs.onpageselected(position); relocation = true; sparsearraycompat<scrolltabholder> scrolltabholders = adapter.getscrolltabholders(); scrolltabholder currentholder = scrolltabholders.valueat(position); if (need_relayout) { currentholder.adjustscroll((int) (header.getheight() + headertop));// 修正滚出去的偏移量 } else { currentholder.adjustscroll((int) (header.getheight() + viewhelper.gettranslationy(header)));// 修正滚出去的偏移量 } } @override public void onpagescrollstatechanged(int state) { tabs.onpagescrollstatechanged(state); } @override public void adjustscroll(int scrollheight) { } private boolean relocation = false; private int headerscrollsize = 0; public static final boolean need_relayout = integer.valueof(build.version.sdk).intvalue() < build.version_codes.honeycomb; private int headertop = 0; // 刷新头部显示时,没有onscroll回调,只有当刷新时会有 @override public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount, int pageposition) { if (viewpager.getcurrentitem() != pageposition) { return; } if (headerscrollsize == 0 && relocation) { relocation = false; return; } relocation = false; int scrolly = math.max(-getscrolly(view), headertranslationdis); if (need_relayout) { headertop = scrolly; header.post(new runnable() { @override public void run() { log.e("main", "scorry1="+ headertop); header.layout(0, headertop, header.getwidth(), headertop + header.getheight()); } }); } else { viewhelper.settranslationy(header, scrolly); } } /** * 主要算这玩意,pulltorefreshlistview插入了一个刷新头部,因此要根据不同的情况计算当前的偏移量</br> * * 当刷新时: 刷新头部显示,因此偏移量要加上刷新头的数值 未刷新时: 偏移量不计算头部。 * * firstvisibleposition >1时,listview中的项开始显示,姑且认为每一项等高来计算偏移量(其实只要显示一个项,向上偏移 * 量已经大于头部的最大偏移量,因此不准确也没有关系) * * @param view * @return */ public int getscrolly(abslistview view) { view c = view.getchildat(0); if (c == null) { return 0; } int top = c.gettop(); int firstvisibleposition = view.getfirstvisibleposition(); if (firstvisibleposition == 0) { return -top + headerscrollsize; } else if (firstvisibleposition == 1) { return -top; } else { return -top + (firstvisibleposition - 2) * c.getheight() + headerheight; } } // 与onheadscroll互斥,不能同时执行 @override public void onheaderscroll(boolean isrefreashing, int value, int pageposition) { if (viewpager.getcurrentitem() != pageposition) { return; } headerscrollsize = value; if (need_relayout) { header.post(new runnable() { @override public void run() { log.e("main", "scorry="+ (-headerscrollsize)); header.layout(0, -headerscrollsize, header.getwidth(), -headerscrollsize + header.getheight()); } }); }else{ viewhelper.settranslationy(header, -value); } } }
解释一下上面的代码,界面中后一层为viewpager,里面加入了多个fragment,每个fragment里面占用一个listivew,listview中添加一个与悬浮头高度完全一样的header,这样可显示区域就为能看到的区域,之后监听listview的onscorll,根据当前显示的listview的item的高度来控制前一层的悬浮的位置,这个地方要注意的时,每次切换tab时,要将当前已经偏移的位置通知到当前切换的tab,比如tab1,向上滑动,影藏了悬浮头,当从tab1切换到tab2时,这是tab2的位置要向上修正,修正距离为悬浮头滑出去的距离。其他的部分代码页比较简单,看看就可以了,其次开源控件pulltorefreshlistview中我修改了当在刷新时偏移的距离,当改距离通知到界面,这样在下拉刷新时,将整个头部向下偏移,
ps:上面的代码中也看到了,我们针对了不同的版本采用了不同的动画,这是由于在3.0以前,位移动画看起来移动了位置,可是实际上控件还在初始位置,为此要针对不同的版本处理不同的动画,否则tab上的点击事件在2.x版本上还是在初始位置。上面的动画采用了nineold控件,也可以自己写,这个部分动画还是比较简单的。
上面的viewpager中添加了fragment,我们来看看tab1listfragment.java
public class tab1listfragment extends scrolltabholderfragment { private pulltorefreshlistview listview; private view placeholderview; private arrayadapter<string> adapter; private arraylist<string> listitems; private handler handler; public tab1listfragment() { this.setfragmentid(pageadaptertab.page_tab1.fragmentid); } @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); } @override public view oncreateview(layoutinflater inflater, viewgroup container, bundle savedinstancestate) { return inflater.inflate(r.layout.page_tab_fragment_layout, container, false); } @override public void onactivitycreated(bundle savedinstancestate) { super.onactivitycreated(savedinstancestate); findviews(); initlistview(); } @suppresslint("inflateparams") private void findviews() { handler = new handler(looper.getmainlooper()); listview = (pulltorefreshlistview) getview().findviewbyid(r.id.page_tab_listview); } private void initlistview() { setlistviewlistener(); listviewaddheader(); listviewloaddata(); } private void setlistviewlistener() { listview.setonrefreshlistener(new onrefreshlistener2<listview>() { @override public void onpulldowntorefresh(pulltorefreshbase<listview> refreshview) { loadnews(); } @override public void onpulluptorefresh(pulltorefreshbase<listview> refreshview) { loadolds(); } }); listview.setonscrolllistener(new onscrolllistener() { @override public void onscrollstatechanged(abslistview view, int scrollstate) { } @override public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount) { if (scrolltabholder != null) { scrolltabholder.onscroll(view, firstvisibleitem, visibleitemcount, totalitemcount, getfragmentid()); } } }); listview.setonheaderscrolllistener(new onheaderscrolllistener() { @override public void onheaderscroll(boolean isrefreashing, boolean istop, int value) { if (scrolltabholder != null && istop) { scrolltabholder.onheaderscroll(isrefreashing, value, getfragmentid()); } } }); } private void listviewaddheader() { placeholderview = new linearlayout(getactivity()); abslistview.layoutparams params = new layoutparams(abslistview.layoutparams.match_parent, getresources() .getdimensionpixelsize(r.dimen.max_header_height)); placeholderview.setlayoutparams(params); listview.getrefreshableview().addheaderview(placeholderview); } protected void listviewloaddata() { listitems = new arraylist<string>(); for (int i = 1; i <= 50; i++) { listitems.add("currnet page: " + (getfragmentid() + 1) + " item --" + i); } adapter = new arrayadapter<string>(getactivity(), r.layout.list_item, android.r.id.text1, listitems); listview.setadapter(adapter); loadnews(); } /** * 下拉清空旧的数据 */ private void loadnews() { handler.postdelayed(new runnable() {// 模拟远程获取数据 @override public void run() { stoprefresh(); // listitems.clear(); // for (int i = 1; i <= 50; i++) { // listitems.add("currnet page: " + (getfragmentid() + // 1) + " item --" + i); // } // notifyadpterdatachanged(); } }, 300); } private void notifyadpterdatachanged() { if (adapter != null) { adapter.notifydatasetchanged(); } } protected void loadolds() { handler.postdelayed(new runnable() {// 模拟远程获取数据 @override public void run() { stoprefresh(); int size = listitems.size() + 1; for (int i = size; i < size + 50; ++i) { listitems.add("currnet page: " + (getfragmentid() + 1) + " item --" + i); } notifyadpterdatachanged(); } }, 300); } // pulltorefreshlistview 自动添加了一个头部 @override public void adjustscroll(int scrollheight) { if (scrollheight == 0 && listview.getrefreshableview().getfirstvisibleposition() >= 2) { return; } //log.d(gettag(), "scrollheight:" + scrollheight); listview.getrefreshableview().setselectionfromtop(2, scrollheight); // log.d(gettag(), "getscrolly:" + getscrolly(listview.getrefreshableview())); // handler.postdelayed(new runnable() { // // @override // public void run() { // log.d(gettag(), "getscrolly:" + getscrolly(listview.getrefreshableview())); // } // }, 5000); } public int getscrolly(abslistview view) { view c = view.getchildat(0); if (c == null) { return 0; } int top = c.gettop(); int firstvisibleposition = view.getfirstvisibleposition(); if (firstvisibleposition == 0) { return -top; } else if (firstvisibleposition == 1) { return top; } else { return -top + (firstvisibleposition - 2) * c.getheight() + 683; } } protected void updatelistview() { if (adapter != null) { adapter.notifydatasetchanged(); } } protected void stoprefresh() { listview.onrefreshcomplete(); } }
上面代码中的界面就是xml中包含了一个pulltorefreshlistview,比较简单这个地方就不贴出来了,我们看到在listviewaddheader中,这个地方添加了一个与悬浮头等高的头部,这样就可以将内容区域给呈现出来,不会被悬浮头遮挡,其次在list的listener中我们将onscorll传到了主界面,这样listview滚动,就可以将当前滚动的距离计算出来,修正悬浮头的距离。
我们再贴出上面剩下的代码scrolltabholderfragment.java与scrolltabholder.java
public abstract class scrolltabholderfragment extends fragment implements scrolltabholder { private int fragmentid; protected scrolltabholder scrolltabholder; public void setscrolltabholder(scrolltabholder scrolltabholder) { this.scrolltabholder = scrolltabholder; } @override public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount, int pageposition) { // nothing } @override public void onheaderscroll(boolean isrefreashing, int value, int pageposition) { } public int getfragmentid() { return fragmentid; } public void setfragmentid(int fragmentid) { this.fragmentid = fragmentid; } } public interface scrolltabholder { void adjustscroll(int scrollheight); void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount, int pageposition); void onheaderscroll(boolean isrefreashing, int value, int pageposition); }
最后我们来看看adaper
public class slidingpageradapter extends fragmentpageradapter { protected final scrolltabholderfragment[] fragments; protected final context context; private sparsearraycompat<scrolltabholder> mscrolltabholders; private scrolltabholder mlistener; public int getcachecount() { return pageadaptertab.values().length; } public slidingpageradapter(fragmentmanager fm, context context, viewpager pager) { super(fm); fragments = new scrolltabholderfragment[pageadaptertab.values().length]; this.context = context; mscrolltabholders = new sparsearraycompat<scrolltabholder>(); init(fm); } private void init(fragmentmanager fm) { for (pageadaptertab tab : pageadaptertab.values()) { try { scrolltabholderfragment fragment = null; list<fragment> fs = fm.getfragments(); if (fs != null) { for (fragment f : fs) { if (f.getclass() == tab.clazz) { fragment = (scrolltabholderfragment) f; break; } } } if (fragment == null) { fragment = (scrolltabholderfragment) tab.clazz.newinstance(); } fragments[tab.tabindex] = fragment; } catch (instantiationexception e) { e.printstacktrace(); } catch (illegalaccessexception e) { e.printstacktrace(); } } } public void settabholderscrollinglistener(scrolltabholder listener) { mlistener = listener; } @override public scrolltabholderfragment getitem(int pos) { scrolltabholderfragment fragment = fragments[pos]; mscrolltabholders.put(pos, fragment); if (mlistener != null) { fragment.setscrolltabholder(mlistener); } return fragment; } public sparsearraycompat<scrolltabholder> getscrolltabholders() { return mscrolltabholders; } @override public int getcount() { return pageadaptertab.values().length; } @override public charsequence getpagetitle(int position) { pageadaptertab tab = pageadaptertab.fromtabindex(position); int resid = tab != null ? tab.resid : 0; return resid != 0 ? context.gettext(resid) : ""; } }
slidingpageradapter 继承自fragmentpageradapter,从主界面传递了一个callback,将在callback传递给每个fragment,这样就将fragment与activity联系起来了。其实还有很多种方式,比如在fragment的attach中获取activity中的回调。上面代码中还有一个pageadaptertab,它又是干什么的呐?来看看代码
public enum pageadaptertab { page_tab1(0, tab1listfragment.class, r.string.page_tab1), page_tab2(1, tab2listfragment.class, r.string.page_tab2), page_tab3(2, tab3listfragment.class, r.string.page_tab3), ; public final int tabindex; public final class<? extends fragment> clazz; public final int resid; public final int fragmentid; private pageadaptertab(int index, class<? extends fragment> clazz, int resid) { this.tabindex = index; this.clazz = clazz; this.resid = resid; this.fragmentid = index; } public static final pageadaptertab fromtabindex(int tabindex) { for (pageadaptertab value : pageadaptertab.values()) { if (value.tabindex == tabindex) { return value; } } return null; } }
就是一个枚举类,配置了当前要显示的fragment,这样以后就要增加就可以只修改改枚举就ok了
到此整个工程就结束了,我们截几张图看看效果:
最后在回顾一下,布局为两层,厚一层为一个viewpager,里面包含了多个fragment,前一层为一个悬浮头与切换tab,当滑动listview时将当前显示的位置传递到主界面,同时更改主界面的位置。
代码地址如下:https://github.com/freesunny/refreashtabview
以上就是本文的全部内容,希望对大家的学习有所帮助。
上一篇: 用CSS动态控制文本属性
下一篇: 浅析java创建文件和目录
推荐阅读
-
Android多个TAB选项卡切换效果
-
Android画廊效果之ViewPager显示多个图片
-
Android Listview多tab上滑悬浮效果
-
Android实现类似网易新闻选项卡动态滑动效果
-
Android 中使用 ViewPager实现屏幕页面切换和页面轮播效果
-
Android开发中ViewPager实现多页面切换效果
-
Android App中制作仿MIUI的Tab切换效果的实例分享
-
Android应用中利用ViewPager实现多页面滑动切换效果示例
-
Android App中使用ViewPager+Fragment实现滑动切换效果
-
Android应用中使用ViewPager实现类似QQ的界面切换效果