Android多個TAB選項卡切換效果
在前一期中,我們做了懸浮頭部的兩個tab切換和下拉刷新效果,后來項目中要求改成三個tab,當(dāng)時就能估量了一下,如果從之前的改,也不是不可以,但是要互相記住的狀態(tài)就太多了,很容易出現(xiàn)錯誤。就決定重新實現(xiàn)一下這個效果,為此先寫了一個demo,這期間項目都已經(jīng)又更新了兩個版本了。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切換,在這大家應(yīng)該都想到了會怎么樣實現(xiàn),Viewpager中添加已經(jīng)fragment,每個fragment里面加入一個可下拉刷新的Listview,根據(jù)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回調(diào),只有當(dāng)刷新時會有
@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插入了一個刷新頭部,因此要根據(jù)不同的情況計算當(dāng)前的偏移量</br>
*
* 當(dāng)刷新時: 刷新頭部顯示,因此偏移量要加上刷新頭的數(shù)值 未刷新時: 偏移量不計算頭部。
*
* firstVisiblePosition >1時,listview中的項開始顯示,姑且認(rèn)為每一項等高來計算偏移量(其實只要顯示一個項,向上偏移
* 量已經(jīng)大于頭部的最大偏移量,因此不準(zhǔn)確也沒有關(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互斥,不能同時執(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,里面加入了多個fragment,每個fragment里面占用一個Listivew,Listview中添加一個與懸浮頭高度完全一樣的header,這樣可顯示區(qū)域就為能看到的區(qū)域,之后監(jiān)聽Listview的onScorll,根據(jù)當(dāng)前顯示的listview的item的高度來控制前一層的懸浮的位置,這個地方要注意的時,每次切換tab時,要將當(dāng)前已經(jīng)偏移的位置通知到當(dāng)前切換的tab,比如tab1,向上滑動,影藏了懸浮頭,當(dāng)從tab1切換到tab2時,這是tab2的位置要向上修正,修正距離為懸浮頭滑出去的距離。其他的部分代碼頁比較簡單,看看就可以了,其次開源控件PullToRefreshListView中我修改了當(dāng)在刷新時偏移的距離,當(dāng)改距離通知到界面,這樣在下拉刷新時,將整個頭部向下偏移
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();
}
/**
* 下拉清空舊的數(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 自動添加了一個頭部
@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中,這個地方添加了一個與懸浮頭等高的頭部,這樣就可以將內(nèi)容區(qū)域給呈現(xiàn)出來,不會被懸浮頭遮擋,其次在list的listener中我們將onScorll傳到了主界面,這樣Listview滾動,就可以將當(dāng)前滾動的距離計算出來,修正懸浮頭的距離。
我們再貼出上面剩下的代碼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,從主界面?zhèn)鬟f了一個callback,將在callback傳遞給每個fragment,這樣就將fragment與activity聯(lián)系起來了。其實還有很多種方式,比如在fragment的attach中獲取activity中的回調(diào)。上面代碼中還有一個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;
}
}
就是一個枚舉類,配置了當(dāng)前要顯示的fragment,這樣以后就要增加就可以只修改改枚舉就ok了
到此整個工程就結(jié)束了,我們截幾張圖看看效果:

最后在回顧一下,布局為兩層,厚一層為一個Viewpager,里面包含了多個fragment,前一層為一個懸浮頭與切換tab,當(dāng)滑動listview時將當(dāng)前顯示的位置傳遞到主界面,同時更改主界面的位置。
代碼地址如下:https://github.com/FreeSunny/RefreashTabView
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助。
- Android TabHost如何實現(xiàn)頂部選項卡
- Android開發(fā)之TabHost選項卡及相關(guān)疑難解決方法
- Android TabHost選項卡標(biāo)簽圖標(biāo)始終不出現(xiàn)的解決方法
- Android組件TabHost實現(xiàn)頁面中多個選項卡切換效果
- android TabHost(選項卡)的使用方法
- android 選項卡(TabHost)如何放置在屏幕的底部
- Android TabLayout(選項卡布局)簡單用法實例分析
- Android實現(xiàn)底部導(dǎo)航欄功能(選項卡)
- Android仿微信底部實現(xiàn)Tab選項卡切換效果
- android選項卡TabHost功能用法詳解
相關(guān)文章
React Native中Android物理back鍵按兩次返回鍵即退出應(yīng)用
這篇文章主要給大家介紹了關(guān)于React Native中Android物理back鍵按兩次返回鍵即退出應(yīng)用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10
Android使用Matrix旋轉(zhuǎn)圖片模擬碟片加載過程
這篇文章主要為大家詳細(xì)介紹了Android使用Matrix旋轉(zhuǎn)圖片模擬碟片加載過程,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03
談?wù)凙ndroid開發(fā)之RecyclerView的使用全解
這篇文章主要介紹了談?wù)凙ndroid開發(fā)之RecyclerView的使用全解,非常具有實用價值,需要的朋友可以參考下。2016-12-12
Android開發(fā)之HttpClient異步請求數(shù)據(jù)的方法詳解【附demo源碼下載】
這篇文章主要介紹了Android開發(fā)之HttpClient異步請求數(shù)據(jù)的方法,結(jié)合實例形式較為詳細(xì)的分析了Android HttpClient異步請求數(shù)據(jù)的相關(guān)操作技巧,并附帶完整demo源碼供讀者下載參考,需要的朋友可以參考下2017-11-11

