Activity中包含Fragment的标签滑动页面的实现详解

移动开发中很多界面是用一个Activity里面包含一个可滑动的头部及与之对应的Fragment来实现的,比如常见的新闻APP中新闻列表就是这样。头部是分类,分类对应的Fragment中展示新闻的列表。这种界面通常的做法是用Indicator+ViewPager的组合来实现。


效果图

这里写图片描述

引用的第三方库

Indicator引用了Github上JakeWharton的ViewPagerIndicator library

示例代码

1.Activity

package com.app.acoe.demo.activity;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;
import android.widget.TextView;

import com.app.acoe.demo.R;
import com.app.acoe.demo.adapter.TabPagerAdapter;
import com.viewpagerindicator.TabPageIndicator;

/**
 * @author Acoe
 * @date 2016-3-24
 * @version V1.0.0
 */
public class TabPagerDemoActivity extends FragmentActivity {
    /**控件*/
    private TextView txtTitle;
    private TabPageIndicator indicator;
    private ViewPager viewPager;
    /**适配器*/
    private TabPagerAdapter pagerAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tab_pager_demo_activity);

        initUI();
    }

    /**
     * 初始化界面
     */
    private void initUI() {
        // 标题
        txtTitle = (TextView) findViewById(R.id.title_textview);
        txtTitle.setText("TabPager");
        // 控件
        indicator = (TabPageIndicator) findViewById(R.id.indicator);
        viewPager = (ViewPager) findViewById(R.id.pager);
        // 设置pager
        viewPager.setOffscreenPageLimit(1);
        pagerAdapter = new TabPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(pagerAdapter);
        indicator.setViewPager(viewPager);
    }
}

2.Activity的布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/main_bg_color"
    android:orientation="vertical" >

    <include layout="@layout/title_bar_view" />

    <com.viewpagerindicator.TabPageIndicator 
       android:id="@+id/indicator"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="15dip"
        android:paddingRight="15dip" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        style="@style/scroll_style" />

</LinearLayout>

3.PagerAdapter代码

适配器继承于V4包的FragmentPagerAdapter

package com.app.acoe.demo.adapter;

import com.app.acoe.demo.fragment.TabPagerFragment;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

/**
 * @author Acoe
 * @date 2016-3-24
 * @version V1.0.0
 */
public class TabPagerAdapter extends FragmentPagerAdapter {
    private String lables[];

    /**
     * @param fm
     */
    public TabPagerAdapter(FragmentManager fm) {
        super(fm);
        this.lables = new String[] { "第一页", "第二页", "第三页" };
    }

    @Override
    public Fragment getItem(int position) {
        return TabPagerFragment.getInstance(position);
    }

    @Override
    public int getCount() {
        return lables == null ? 0 : lables.length;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return lables[position];
    }

}

4.Fragment代码

package com.app.acoe.demo.fragment;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.app.acoe.demo.R;

/**
 * @author Acoe
 * @date 2016-3-24
 * @version V1.0.0
 */
public class TabPagerFragment extends Fragment {
    private int position;
    private View nowView;
    private TextView txtView;

    private TabPagerFragment() {
    }

    public static TabPagerFragment getInstance(int position) {
        TabPagerFragment fragment = new TabPagerFragment();
        Bundle bundle = new Bundle();
        bundle.putInt("position", position);
        fragment.setArguments(bundle);
        return fragment;
    }

    @SuppressLint("InflateParams")
    @Override
    public View onCreateView(LayoutInflater inflater,
            @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        nowView = inflater.inflate(R.layout.pager_fragment, null);
        return nowView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initUI();
    }

    /**
     * 初始化控件
     */
    private void initUI() {
        this.position = getArguments().getInt("position");
        this.txtView = (TextView) nowView.findViewById(R.id.textview);
        this.txtView.setText("第" + (position+1) + "个Fragment");
    }

}

通常在实际中我们把传入给Fragment的bundle中放入的是一个关键性的对象,比如新闻类的传入栏目id,这样可以在Fragment给界面加载数据的时候,根据id来判断加载的是哪个标签(就是Adapter中的lables)下面的内容。实际上,在Adapter中的lables通常放的是一个存放着新闻栏目的ArrayList,栏目的名称、id都存放在这个list里面。

5.TabPageIndicator的样式修改

实际开发中,设计总会有定制的头部(就是上面图片中显示“第一页”、“第二页”那些),比如颜色或者图标、边框这些有特殊的邀请,作为开发我们有时候就需要调整代码。那么怎么办呢?请看下面:
1)上面说的那些其实就是个样式问题,那么样式当然是在styles文件里面,如下是我给TabPageIndicator使用的样式

<!-- tab indicator样式 -->
    <style name="MyTheme_tab" parent="@android:style/Theme.NoTitleBar">
        <item name="vpiTabPageIndicatorStyle">@style/MyWidget.TabPageIndicator</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:animationDuration">5000</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:textColor">@drawable/viewpager_title_textcolor</item>
    </style>

    <style name="MyWidget.TabPageIndicator" parent="Widget">
        <item name="android:gravity">center</item>
        <item name="android:background">@drawable/tab_blue_indicator</item>
        <item name="android:paddingTop">15dp</item>
        <item name="android:paddingBottom">15dp</item>
        <item name="android:textAppearance">@style/MyTextAppearance.TabPageIndicator</item>
        <item name="android:textSize">@dimen/normal_text_size</item>
        <item name="android:maxLines">1</item>
    </style>

    <style name="MyTextAppearance.TabPageIndicator" parent="Widget">
        <item name="android:textStyle">normal</item>
        <item name="android:textColor">@android:color/black</item>
    </style>

2)其实只需要一个就可以了,但是实际项目中可能有很多界面用到这种滑动界面,所以采用分级的style样式来定制出指定的效果。这样可以打造不同的头部效果来。
(1)

<style name="MyTextAppearance.TabPageIndicator" parent="Widget">
        <item name="android:textStyle">normal</item>
        <item name="android:textColor">@android:color/black</item>
    </style>

上面这一段我规定了所有头部的样式,normal是我自己定义的一个TextView的样式,包含了layout_width、layout_height、textSize这些属性。没错TabPageIndicator就是一个TextView列表,里面单个标签就是一个TextView。所以实际就是在对TextView的样式进行修改。
(2)

<style name="MyWidget.TabPageIndicator" parent="Widget">
        <item name="android:gravity">center</item>
        <item name="android:background">@drawable/tab_blue_indicator</item>
        <item name="android:paddingTop">15dp</item>
        <item name="android:paddingBottom">15dp</item>
        <item name="android:textAppearance">@style/MyTextAppearance.TabPageIndicator</item>
        <item name="android:textSize">@dimen/normal_text_size</item>
        <item name="android:maxLines">1</item>
    </style>

这上面规定了头部每个标签是居中显示的,以及背景样式。tab_blue_indicator的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Non focused states -->
    <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/tab_unselected_bg" />
    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/tab_selected_bg" />

    <!-- Focused states -->
    <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/tab_unselected_bg" />
    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/tab_selected_bg" />

    <!-- Pressed -->
    <!--    Non focused states -->
    <item android:state_focused="false" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_bg" />
    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/tab_selected_bg" />

    <!--    Focused states -->
    <item android:state_focused="true" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_bg" />
    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/tab_selected_bg" />
</selector>

这其实就是2张.9图,用来做出选中和未选中状态下的下划线效果
这里写图片描述 这里写图片描述
然后两个padding属性来控制头部的高度,两个标签直接的左右间隔可以通过paddingLeft和paddingRight来控制。
(3)

<style name="MyTheme_tab" parent="@android:style/Theme.NoTitleBar">
        <item name="vpiTabPageIndicatorStyle">@style/MyWidget.TabPageIndicator</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:animationDuration">5000</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:textColor">@drawable/viewpager_title_textcolor</item>
    </style>

第一行知道这个style是给Activity用的后就很好了解,第二行name是固定写法,指明PageIndicator的样式是我们刚才定义的那个MyWidget.TabPageIndicator。

<item name="android:textColor">@drawable/viewpager_title_textcolor</item>

这一行就是定义标签被点击时的文字样式,看效果图,我设置的是按下显示蓝色。
如果是需要在头部样式里显示图片背景可以设置background为图片,或者文字加图片用drawableLeft、drawableRight……来设置。其实就是把TextView设置出你想要的效果就行了。

6.让样式生效

在Mainfest.xml文件中给Activity的theme设置成我们上面写好的样式就行了,如下。

<activity 
            android:name="com.app.acoe.demo.activity.TabPagerDemoActivity"
            android:theme="@style/MyTheme_tab">
        </activity>

7.扩展解读

然后如果只是上面那样写Adapter的话,在有种情况下会出问题。就是当lables内容变化时,Fragment会发生错位,这是由于Fragment重用导致的,使得lables内容改变后Fragment和lables的对应关系没得到更新。那么如何解决呢?

/**
     * 由于fragment存在重用问题,tab增加或减少以后,按先后顺序新重用旧的fragment,导致tab标签和fragment内容不一致(错位现象)
     * 重写instantiateItem,调用fragment的setter方法,setter方法中不能操作view,这些 View 只有在 onCreateView()事件后才能操作
     * 根据setter的数据在fragment界面加载完成后刷新数据
     */
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        InformationListFragment fragment = (InformationListFragment) super.instantiateItem(container, position);
        fragment.setChannelId(lables.get(position).columnValueEnum);
        return fragment;
    }

    /**
     * 重写instantiateItem()时,此方法也必须重写,使得instantiateItem()被执行到
     */
    @Override
    public int getItemPosition(Object object) {
        return PagerAdapter.POSITION_NONE;
    }

如上,是我从项目中FragmentPagerAdapter代码里截取的。这样可以在lables内容发生变化时,将对应的Fragment中传递过去的(演示代码所示的)position也好、(实际中的)id也好,进行更新。然后在Fragment中的onResume()方法里判断position值、或者id值是否发生了变化,如果是,则重新对界面数据进行请求加载刷新这样的步骤。

源码基本上全部贴出来,就不附项目源码了。除了部分样式,这个自己随便写一个就行了,没必要完全复制。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值