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

Android多个TAB选项卡切换效果

程序员文章站 2024-03-01 11:47:22
在前一期中,我们做了悬浮头部的两个tab切换和下拉刷新效果,后来项目中要求改成三个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了

到此整个工程就结束了,我们截几张图看看效果:

Android多个TAB选项卡切换效果

最后在回顾一下,布局为两层,厚一层为一个viewpager,里面包含了多个fragment,前一层为一个悬浮头与切换tab,当滑动listview时将当前显示的位置传递到主界面,同时更改主界面的位置。

代码地址如下:https://github.com/freesunny/refreashtabview

以上就是本文的全部内容,希望对大家的学习有所帮助。