在前一期中,我們做了懸浮頭部的兩個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
以上就是本文的全部內容,希望對大家的學習有所幫助。
新聞熱點
疑難解答