Android多個(gè)TAB選項(xiàng)卡切換效果
在前一期中,我們做了懸浮頭部的兩個(gè)tab切換和下拉刷新效果,后來(lái)項(xiàng)目中要求改成三個(gè)tab,當(dāng)時(shí)就能估量了一下,如果從之前的改,也不是不可以,但是要互相記住的狀態(tài)就太多了,很容易出現(xiàn)錯(cuò)誤。就決定重新實(shí)現(xiàn)一下這個(gè)效果,為此先寫(xiě)了一個(gè)demo,這期間項(xiàng)目都已經(jīng)又更新了兩個(gè)版本了。demo還木有變成文章。
之前的版本中是采用了一個(gè)可以下拉刷新的listview,之后在listview中添加了兩個(gè)頭部,并且在該布局上的上面用了一個(gè)一模一樣的切換tab,如果沒(méi)有看過(guò)前面版本的,可以看看前一個(gè)版本,Listview多Tab上滑懸浮。
基于上述思路我們先來(lái)看看頁(yè)面布局: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>
頁(yè)面采用了兩層,后面一層為Viewpager,前面為懸浮頭與tab切換,在這大家應(yīng)該都想到了會(huì)怎么樣實(shí)現(xiàn),Viewpager中添加已經(jīng)fragment,每個(gè)fragment里面加入一個(gè)可下拉刷新的Listview,根據(jù)ListView的滑動(dòng)來(lái)控制前一幀頁(yè)面的位置。
來(lái)看看頁(yè)面代碼吧,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);//控制頁(yè)面上滑 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; // 刷新頭部顯示時(shí),沒(méi)有onScroll回調(diào),只有當(dāng)刷新時(shí)會(huì)有 @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插入了一個(gè)刷新頭部,因此要根據(jù)不同的情況計(jì)算當(dāng)前的偏移量</br> * * 當(dāng)刷新時(shí): 刷新頭部顯示,因此偏移量要加上刷新頭的數(shù)值 未刷新時(shí): 偏移量不計(jì)算頭部。 * * firstVisiblePosition >1時(shí),listview中的項(xiàng)開(kāi)始顯示,姑且認(rèn)為每一項(xiàng)等高來(lái)計(jì)算偏移量(其實(shí)只要顯示一個(gè)項(xiàng),向上偏移 * 量已經(jīng)大于頭部的最大偏移量,因此不準(zhǔn)確也沒(méi)有關(guān)系) * * @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互斥,不能同時(shí)執(zhí)行 @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,里面加入了多個(gè)fragment,每個(gè)fragment里面占用一個(gè)Listivew,Listview中添加一個(gè)與懸浮頭高度完全一樣的header,這樣可顯示區(qū)域就為能看到的區(qū)域,之后監(jiān)聽(tīng)Listview的onScorll,根據(jù)當(dāng)前顯示的listview的item的高度來(lái)控制前一層的懸浮的位置,這個(gè)地方要注意的時(shí),每次切換tab時(shí),要將當(dāng)前已經(jīng)偏移的位置通知到當(dāng)前切換的tab,比如tab1,向上滑動(dòng),影藏了懸浮頭,當(dāng)從tab1切換到tab2時(shí),這是tab2的位置要向上修正,修正距離為懸浮頭滑出去的距離。其他的部分代碼頁(yè)比較簡(jiǎn)單,看看就可以了,其次開(kāi)源控件PullToRefreshListView中我修改了當(dāng)在刷新時(shí)偏移的距離,當(dāng)改距離通知到界面,這樣在下拉刷新時(shí),將整個(gè)頭部向下偏移
ps:上面的代碼中也看到了,我們針對(duì)了不同的版本采用了不同的動(dòng)畫(huà),這是由于在3.0以前,位移動(dòng)畫(huà)看起來(lái)移動(dòng)了位置,可是實(shí)際上控件還在初始位置,為此要針對(duì)不同的版本處理不同的動(dòng)畫(huà),否則tab上的點(diǎn)擊事件在2.X版本上還是在初始位置。上面的動(dòng)畫(huà)采用了nineold控件,也可以自己寫(xiě),這個(gè)部分動(dòng)畫(huà)還是比較簡(jiǎn)單的。
上面的Viewpager中添加了Fragment,我們來(lái)看看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(); } /** * 下拉清空舊的數(shù)據(jù) */ private void loadNews() { handler.postDelayed(new Runnable() {// 模擬遠(yuǎn)程獲取數(shù)據(jù) @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() {// 模擬遠(yuǎn)程獲取數(shù)據(jù) @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 自動(dòng)添加了一個(gè)頭部 @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中包含了一個(gè)PullToRefreshListView,比較簡(jiǎn)單這個(gè)地方就不貼出來(lái)了,我們看到在listViewAddHeader中,這個(gè)地方添加了一個(gè)與懸浮頭等高的頭部,這樣就可以將內(nèi)容區(qū)域給呈現(xiàn)出來(lái),不會(huì)被懸浮頭遮擋,其次在list的listener中我們將onScorll傳到了主界面,這樣Listview滾動(dòng),就可以將當(dāng)前滾動(dòng)的距離計(jì)算出來(lái),修正懸浮頭的距離。
我們?cè)儋N出上面剩下的代碼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); }
最后我們來(lái)看看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,從主界面?zhèn)鬟f了一個(gè)callback,將在callback傳遞給每個(gè)fragment,這樣就將fragment與activity聯(lián)系起來(lái)了。其實(shí)還有很多種方式,比如在fragment的attach中獲取activity中的回調(diào)。上面代碼中還有一個(gè)PageAdapterTab,它又是干什么的吶?來(lái)看看代碼
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; } }
就是一個(gè)枚舉類(lèi),配置了當(dāng)前要顯示的fragment,這樣以后就要增加就可以只修改改枚舉就ok了
到此整個(gè)工程就結(jié)束了,我們截幾張圖看看效果:
最后在回顧一下,布局為兩層,厚一層為一個(gè)Viewpager,里面包含了多個(gè)fragment,前一層為一個(gè)懸浮頭與切換tab,當(dāng)滑動(dòng)listview時(shí)將當(dāng)前顯示的位置傳遞到主界面,同時(shí)更改主界面的位置。
代碼地址如下:https://github.com/FreeSunny/RefreashTabView
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
- Android TabHost如何實(shí)現(xiàn)頂部選項(xiàng)卡
- Android開(kāi)發(fā)之TabHost選項(xiàng)卡及相關(guān)疑難解決方法
- Android TabHost選項(xiàng)卡標(biāo)簽圖標(biāo)始終不出現(xiàn)的解決方法
- Android組件TabHost實(shí)現(xiàn)頁(yè)面中多個(gè)選項(xiàng)卡切換效果
- android TabHost(選項(xiàng)卡)的使用方法
- android 選項(xiàng)卡(TabHost)如何放置在屏幕的底部
- Android TabLayout(選項(xiàng)卡布局)簡(jiǎn)單用法實(shí)例分析
- Android實(shí)現(xiàn)底部導(dǎo)航欄功能(選項(xiàng)卡)
- Android仿微信底部實(shí)現(xiàn)Tab選項(xiàng)卡切換效果
- android選項(xiàng)卡TabHost功能用法詳解
相關(guān)文章
Android中ListView的item點(diǎn)擊沒(méi)有反應(yīng)的解決方法
這篇文章主要介紹了Android中ListView的item點(diǎn)擊沒(méi)有反應(yīng)的相關(guān)資料,需要的朋友可以參考下2017-10-10JSON中optString和getString方法的區(qū)別
optString方法會(huì)在對(duì)應(yīng)的key中的值不存在的時(shí)候返回一個(gè)空字符串,但是getString會(huì)拋一個(gè)JSONException 。下面通過(guò)一段代碼給大家介紹JSON中optString和getString方法的區(qū)別,感興趣的朋友一起看看吧2017-07-07Android使用selector修改TextView中字體顏色和背景色的方法
這篇文章主要介紹了Android使用selector修改TextView中字體顏色和背景色的方法,實(shí)例分析了selector方法的相關(guān)使用技巧,需要的朋友可以參考下2016-01-01Android中使用TagFlowLayout制作動(dòng)態(tài)添加刪除標(biāo)簽
這篇文章主要介紹了Android中使用TagFlowLayout制作動(dòng)態(tài)添加刪除標(biāo)簽的步驟詳解,需要的朋友參考下吧2017-07-07Android獲取和讀取短信驗(yàn)證碼的實(shí)現(xiàn)方法
這篇文章主要介紹了Android獲取和讀取短信驗(yàn)證碼的實(shí)現(xiàn)方法,文章內(nèi)容介紹了如何讀取剛收到的短信的相關(guān)內(nèi)容,以及Android獲取短信驗(yàn)證碼的方法,感興趣的小伙伴們可以參考一下2016-03-03Android中SharedPreferences簡(jiǎn)單使用實(shí)例
這篇文章主要介紹了Android中SharedPreferences簡(jiǎn)單使用案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10