介绍三个Android支持库控件:TabLayout+ViewPager+RecyclerView

本文主要介绍如下三个Android支持库控件的配合使用:

  • TabLayout:android.support.design.widget.TabLayout
  • ViewPager:android.support.v4.view.ViewPager
  • RecyclerView:android.support.v7.widget.RecyclerView

对于支持库的使用,这里稍作介绍:
Android支持库包含常用的v4、v7、v13以及v17,注解支持(annotation),设计支持(material design)等。
目前v4和v7已经根据不同的功能进行了拆分,以减小目标Apk的大小,可针对某项具体的功能引入特定的包,而不用整体引入support-v4包。
比如,以下为笔者常用的支持库,读者可以参考:

// 实现各种 UI 相关组件,例如 ViewPager、NestedScrollView 和 ExploreByTouchHelper。
supportCoreUi       : 'com.android.support:support-core-ui:24.2.0',
// 添加对使用片段封装用户界面和功能的支持,从而使应用能够提供可以在大屏幕设备与小屏幕设备之间进行调节的布局。
// 此模块依赖于 compat、core-utils、core-ui 和 media-compat。
supportFragment     : 'com.android.support:support-fragment:24.2.0',
// 此库添加了对操作栏用户界面设计模式的支持。此库包含对 Material Design 用户界面实现的支持。
supportAppCompat    : 'com.android.support:appcompat-v7:24.2.0',
// recyclerview 库添加了 RecyclerView 类。此类能够为 RecyclerView 小部件提供支持,
// RecyclerView 是一种通过提供有限的数据项窗口有效显示大数据集的视图。
supportRecyclerView : 'com.android.support:recyclerview-v7:24.2.0',
// 此库添加了对 CardView 小部件的支持,让您能够在卡片内显示信息,从而使应用具备一致的外观。
supportCardView     : 'com.android.support:cardview-v7:24.2.0',
// 注解软件包提供的 API 支持向应用中添加注解元数据。
supportAnnotations  : 'com.android.support:support-annotations:24.2.0',
// 设计软件包提供的 API 支持向应用中添加 Material Design 组件和模式。
supportDesign       : 'com.android.support:design:24.2.0',

最后,需要注意,支持库的版本需要保持一致,不然容易出现编译错误。
更多关于支持库的使用,请参考官方文档

先上效果图:

Tab页切换效果图

(左滑的比较慢,是为了看清效果)

一、TabLayout

TabLayout是design支持库中引入的支持Tab页的控件,配合ViewPager使用,实现Table页面的滑动。使用时:

  1. 使用setTabMode(TabLayout.MODE_FIXED)来设置TabLayout的模式;
  2. 通过addTab来添加Tab页面;
  3. 最后通过setupWithViewPager来关联ViewPager.
// 设置TabLayout的模式
goodsTypeTl.setTabMode(TabLayout.MODE_FIXED);
// 添加Fragment显示
for (int i = 0; i < 2; i++) {
    PickingTaskGoodsFragment itemFragment = PickingTaskGoodsFragment.newInstance(
            i == 0 ? unCompletedPickingSku : pickingCompletedSku);
    fragmentList.add(itemFragment);
    goodsTypeTl.addTab(goodsTypeTl.newTab().setText(titleList.get(i)));
}
// 实例化ViewPage的适配器
FragmentPagerAdapter fAdapter = new PickingFragmentPagerAdapter(
        getSupportFragmentManager(), fragmentList, titleList);
// viewpager加载adapter
goodsDetailVp.setAdapter(fAdapter);
// TabLayout加载viewpager
goodsTypeTl.setupWithViewPager(goodsDetailVp);

二、ViewPager

ViewPager是在v4包中引入的控件,在布局文件中,紧接着TabLayout进行布局。ViewPager继承自ViewGroup,在使用时,最关键的是为其添加PagerAdapter,一般ViewPage会包含Fragment,那么这里PagerAdapter会使用FragmentPagerAdapter:

public abstract class FragmentPagerAdapter extends PagerAdapter

FragmentPagerAdapter是一个抽象类,在使用时一般继承自FragmentPagerAdapter自定义实现:

public class PickingFragmentPagerAdapter extends FragmentPagerAdapter {

    private List<Fragment> fragmentList;
    private List<String> titleList;

    public PickingFragmentPagerAdapter(FragmentManager fm,
                                       List<Fragment> fragmentList, List<String> titleList) {
        super(fm);
        this.fragmentList = fragmentList;
        this.titleList = titleList;
    }

    @Override
    public Fragment getItem(int position) {
        return fragmentList.get(position);
    }

    @Override
    public int getCount() {
        return titleList.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return titleList.get(position);
    }
}

getItem和getCount方法必须要求实现,分别返回List<Fragment>的内容就行了。

在实例化PickingFragmentPagerAdapter时,需要传入FragmentManager,一般采用getSupportFragmentManager()。

三、RecyclerView

RecyclerView是v7支持库中引入的控件,使用时需要依赖com.android.support:recyclerview-v7:22.2.1,使用RecyclerView需要注意两点:

  1. 设置LayoutManager
recyclerView.setLayoutManager(new LinearLayoutManager(context));

当然,这里还可以设置GridLayoutManager
2. 设置Adapter

recyclerView.setAdapter(new PickingTaskGoodsAdapter(pickingTaskDParams, mListener));

RecyclerView的Adapter是需要重点关注的。其中,需要实现的方法有三个:onCreateViewHolderonBindViewHoldergetItemCount

  • onCreateViewHolder:这个方法的返回值是ViewHolder,而这个ViewHolder一般都需要自定义,当然,默认RecyclerView也有ViewHolder。
View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.out_picking_goods_detail_item, parent, false);
return new ViewHolder(view, new MySkuItemData());

View是直接通过Inflater提取出来的(注意item的根ViewGroup的height不能设置为match parent,不然多行item无法正常显示),ViewHolder的入参需要传入View。ViewHolder的定义有点讲究,与ListView中有不一致:

public class ViewHolder extends RecyclerView.ViewHolder {
    final View mView;

    AutofitTextView goodsNumberTv;
    TextView goodsNameTv;
    TextView goodsLeftNumberTv;
    ImageView skuState;
    Button pickingSku;

    ViewHolder(View view) {
        super(view);
        mView = view;

        goodsNumberTv = (AutofitTextView) view.findViewById(R.id.out_picking_task_goods_number_tv);
        goodsNameTv = (TextView) view.findViewById(R.id.out_picking_task_goods_name_tv);
        goodsLeftNumberTv = (TextView) view.findViewById(R.id.out_picking_task_goods_left_number_tv);
        skuState = (ImageView) view.findViewById(R.id.out_picking_task_goods_sku_state_iv);
        pickingSku = (Button) view.findViewById(R.id.out_picking_task_goods_picking_btn);
    }
}

首先,需要定义一个带参构造器,第一个参数一定是View,ViewHolder也需要包含View的域,另外,ViewHolder可以包含各个View的监听器,而这种监听器一般都需要自定义,因为其中会包含重要的参数。

  • onBindViewHolder是具体实现数据更新的地方,onBindViewHolder的入参为ViewHolder holder, int position,因此,首先通过position获取数据,然后对ViewHolder的控件依次设置:
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    PickingTaskDParam dParam = taskDParamList.get(position);
    holder.goodsNumberTv.setText(dParam.getSkuNo());
    holder.goodsNameTv.setText(dParam.getSkuName());
    holder.goodsLeftNumberTv.setText(String.valueOf(dParam.getPlannedPickQty().intValue()));

    if (dParam.getOptStatus() == 1 || dParam.getOptStatus() == 2) {
        // 大小设置
        holder.pickingSku.setVisibility(View.GONE);
        holder.skuState.setVisibility(View.VISIBLE);
    } else {
        holder.skuState.setVisibility(View.GONE);
        if (pickingVisible) {
            holder.pickingSku.setVisibility(View.VISIBLE);
            holder.pickingSku.setOnClickListener(new MyItemOnClickListener(dParam));
        } else {
            holder.pickingSku.setVisibility(View.GONE);
        }
    }
}
  • getItemCount就直接返回数据List的size()就可以啦。

RecyclerView添加分割:分为设置垂直方向距离和设置分隔条两种方式:

垂直方向距离:

public class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {

    private final int verticalSpaceHeight;

    public VerticalSpaceItemDecoration(int verticalSpaceHeight) {
        this.verticalSpaceHeight = verticalSpaceHeight;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
            RecyclerView.State state) {
        outRect.bottom = verticalSpaceHeight;
    }
}

分隔条:

public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};

    private Drawable divider;

    /**
     * Default divider will be used
     */
    public DividerItemDecoration(Context context) {
        final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
        divider = styledAttributes.getDrawable(0);
        styledAttributes.recycle();
    }

    /**
     * Custom divider will be used
     */
    public DividerItemDecoration(Context context, int resId) {
        divider = ContextCompat.getDrawable(context, resId);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();

        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);

            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();

            int top = child.getBottom() + params.bottomMargin;
            int bottom = top + divider.getIntrinsicHeight();

            divider.setBounds(left, top, right, bottom);
            divider.draw(c);
        }
    }
}

以上关于分隔条内容可参考Stack Overflow:How to add dividers and spaces between items in RecyclerView?

四、One more thing

最后,复习下Fragment和Activity的数据传递:

  • 从Activity到Fragment:Bundle,通过Fragment的newInstance方法:
public static PickingTaskGoodsFragment newInstance(List<PickingTaskDParam> pickingTaskDParams) {
    PickingTaskGoodsFragment fragment = new PickingTaskGoodsFragment();
    Bundle args = new Bundle();
    args.putParcelable(PICKING_TASK_D_PARAM, Parcels.wrap(pickingTaskDParams));
    fragment.setArguments(args);
    return fragment;
}
  • 从Fragment到Activity:接口回调,在Fragment中定义接口,让包含该Fragment的Activity必须实现接口:
@Override
public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnListFragmentInteractionListener) {
        mListener = (OnListFragmentInteractionListener) context;
    } else {
        throw new RuntimeException(context.toString()
                + " must implement OnListFragmentInteractionListener");
    }
}
@Override
public void onDetach() {
    super.onDetach();
    mListener = null;
}
public interface OnListFragmentInteractionListener {

    void onListFragmentInteraction(PickingTaskDParam item);
}

以上。

posted @ 2017-04-14 08:46  yhthu  阅读(3807)  评论(0编辑  收藏  举报