当前位置: 移动技术网 > IT编程>移动开发>Android > Android多个TAB选项卡切换效果

Android多个TAB选项卡切换效果

2019年07月24日  | 移动技术网IT编程  | 我要评论

宋紫琳,蓝莓怎么吃,妖物警司

在前一期中,我们做了悬浮头部的两个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

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网