TabLayout+RecycleView 实现滑动锚点定位

前言

在我们打开淘宝App,查看某个商品详情的时候,我们就会发现如下图所显示的画面。
没错,这就是我们要介绍的效果,也就是TabLayout+RecycleView 实现的滑动锚点定位。
由于是项目急需,所以细节问题不是很精致。之后有时间会对细节问题进行更改。

效果图

在这里插入图片描述

操作如下

1. xml样式布局

  • imgflash:此 ImageView 图片控件是我设置的返回键
  • imgpush:此ImageView 图片控件是设置的分享键
  • ancharTab:此TabLayout 控件,这里是设置分类效果
  • ancharRecy:此RecycleView 控件,这里是设置滑动多布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#fff"
    tools:context=".taobao.AnchorActivity">

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/imgflash"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:layout_marginLeft="16dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:background="@drawable/ic_arrow_back_black_24dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <ImageView
            android:id="@+id/imgpush"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:layout_marginRight="16dp"
            android:layout_marginTop="16dp"
            android:background="@drawable/ic_grain_black_24dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <android.support.design.widget.TabLayout
            android:id="@+id/ancharTab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:tabGravity="center"
            app:layout_constraintBottom_toBottomOf="@+id/imgflash"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@+id/imgflash" />
    </android.support.constraint.ConstraintLayout>


    <android.support.v7.widget.RecyclerView
        android:id="@+id/ancharRecy"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

2. 对应xml的activity编码操作

  • tabTxt:是自定义的tab分类数组
  • mancharTab:是TabLayout 控件的变量名
  • mancharRecy:是RecyclerView 控件的变量名
  • isRecyclerScroll:判读是否是recyclerView主动引起的滑动而设置的boolean变量
  • lastPos:记录上一次位置,防止在同一内容块里滑动 重复定位到tablayout而设置的int变量
  • canScroll:用于recyclerView滑动到指定的位置而设置的boolean变量
  • scrollToPosition:记录上一次位置,防止在同一内容块里滑动 重复定位到recycle View而设置的int变量
  • manager:是recycleView设置布局管理器的变量名
  • initView():此方法是获取对应控件ID设置变量名
  • initData():此方法是赋值
  • initAnchor():此方法是分别对tab和recycle进行锚点定位实现联动
public class AnchorActivity extends AppCompatActivity {
    //自定义Tab分类
    private String[] tabTxt = {"商品", "评论", "详情"};
    private TabLayout mancharTab;
    private RecyclerView mancharRecy;
    //判读是否是recyclerView主动引起的滑动,true- 是,false- 否,由tablayout引起的
    private boolean isRecyclerScroll;
    //记录上一次位置,防止在同一内容块里滑动 重复定位到tablayout
    private int lastPos;
    //用于recyclerView滑动到指定的位置
    private boolean canScroll;
    private int scrollToPosition;
    private LinearLayoutManager manager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_anchor);
        initView();
        initData();
        initAnchor();
    }

    private void initAnchor() {
		//设置布局管理器
        manager= new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        //recycleView和布局管理器进行关联
        mancharRecy.setLayoutManager(manager);
        //recycleView关联并设置适配器
        mancharRecy.setAdapter(new AnchorAdapter(this, tabTxt));
		
		//tablayout滑动监听
        mancharTab.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                //点击标签,使recyclerView滑动,isRecyclerScroll置false
                int pos = tab.getPosition();
                isRecyclerScroll = false;
                moveToPosition(manager, mancharRecy, pos);
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });
		
		//获取RecycleView的点击操控
        mancharRecy.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //当滑动由recyclerView触发时,isRecyclerScroll 置true
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    isRecyclerScroll = true;
                }
                return false;
            }
        });

		//recycleView设置滑动监听
        mancharRecy.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (canScroll) {
                    canScroll = false;
                    moveToPosition(manager, recyclerView, scrollToPosition);
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (isRecyclerScroll) {
                    //第一个可见的view的位置,即tablayou需定位的位置
                    int position = manager.findFirstVisibleItemPosition();
                    if (lastPos != position) {
                        mancharTab.setScrollPosition(position, 0, true);
                    }
                    lastPos = position;
                }
            }
        });

    }

    private void initData() {
        //tablayout设置标签赋值
        for (int i = 0; i < tabTxt.length; i++) {
            mancharTab.addTab(mancharTab.newTab().setText(tabTxt[i]));
        }
    }

    private void initView() {
        mancharTab = (TabLayout) findViewById(R.id.ancharTab);
        mancharRecy = (RecyclerView) findViewById(R.id.ancharRecy);
    }


    public void moveToPosition(LinearLayoutManager manager, RecyclerView mRecyclerView, int position) {
        // 第一个可见的view的位置
        int firstItem = manager.findFirstVisibleItemPosition();
        // 最后一个可见的view的位置
        int lastItem = manager.findLastVisibleItemPosition();
        if (position <= firstItem) {
            // 如果跳转位置firstItem 之前(滑出屏幕的情况),就smoothScrollToPosition可以直接跳转,
            mRecyclerView.smoothScrollToPosition(position);
        } else if (position <= lastItem) {
            // 跳转位置在firstItem 之后,lastItem 之间(显示在当前屏幕),smoothScrollBy来滑动到指定位置
            int top = mRecyclerView.getChildAt(position - firstItem).getTop();
            mRecyclerView.smoothScrollBy(0, top);
        } else {
            // 如果要跳转的位置在lastItem 之后,则先调用smoothScrollToPosition将要跳转的位置滚动到可见位置
            // 再通过onScrollStateChanged控制再次调用当前moveToPosition方法,执行上一个判断中的方法
            mRecyclerView.smoothScrollToPosition(position);
            scrollToPosition = position;
            canScroll = true;
        }
    }

3. recycleView设置的适配器

  • ITEMONE,ITEMTWO ,ITEMTHREE :这三个变量分别代表三个不同的自布局
  • tabTxt:在步骤2中提到的 自定义的tab分类数组
  • 适配器中重写了onCreateViewHolder、onBindViewHolder、getItemCount、getItemViewType这几个方法
    1) onCreateViewHolder:创建布局,加载item
    2) onBindViewHolder:绑定事件
    3) getItemCount:总条目数量
    4) getItemViewType:根据条件判断返回不同的int值,之后进入 onCreateViewHolder 方法中判断,返回的int值即 等于 onCreateViewHolder 中 viewType 的值,然后通过进行判断返回不同类型的布局
  • OneItemHolder,TwoItemHolder, ThreeItemHolder:这是每一个布局的viewHolder,要继承自RecyclerView.ViewHolder,在这里可以分别绑定每个子xml的控件
  • R.layout.anchor_one,R.layout.anchor_two,R.layout.anchor_three:这三个layout是我们recycleView多布局的三个子item (在这里就不给大家粘贴了 可以自己根据xml名称在对应界面进行布局)
public class AnchorAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    //三个final分别代表三个不同的布局
    public static final int ITEMONE = 1;
    public static final int ITEMTWO = 2;
    public static final int ITEMTHREE = 3;

    //上下文
    private Context context;
    private String[] tabTxt;

    /**
     * 构造方法
     *
     * @param context
     */
    public AnchorAdapter(Context context) {
        this.context = context;
    }
    
    public AnchorAdapter(Context context, String[] tabTxt) {
        this.context = context;
        this.tabTxt = tabTxt;
    }


    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View inflate = null;
        RecyclerView.ViewHolder viewHolder = null;

        //根据viewType返回不同布局
        switch (viewType) {
            case ITEMONE:
                inflate = LayoutInflater.from(context).inflate(R.layout.anchor_one, parent, false);
                viewHolder = new OneItemHolder(inflate);
                break;
            case ITEMTWO:
                inflate = LayoutInflater.from(context).inflate(R.layout.anchor_two, parent, false);
                viewHolder = new ThreeItemHolder(inflate);
                break;
            case ITEMTHREE:
                inflate = LayoutInflater.from(context).inflate(R.layout.anchor_three, parent, false);
                viewHolder = new TwoItemHolder(inflate);
                break;
            default:
                break;

        }
        //返回布局
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
        //如果当前的 viewHolder 属于 OneItemHolder 则执行
        if (viewHolder instanceof OneItemHolder) {
            /**
             * TYPE_ONE 的操作内容
             */

        } else if (viewHolder instanceof TwoItemHolder) {
            /**
             * TYPE_TWO 的操作内容
             */

        } else if (viewHolder instanceof ThreeItemHolder) {
            /**
             * TYPE_THREE 的操作内容
             */

        }
    }

    @Override
    public int getItemCount() {
        return tabTxt.length;
    }
    /**
     * 返回条目类型(这里就做个简单的判断区分)
     *
     * @param position 代表第几个条目
     * @return
     */
    @Override
    public int getItemViewType(int position) {

        if (position % 3 == 0) {
            return ITEMONE;
        } else if (position % 2 == 0) {
            return ITEMTWO;
        } else {
            return ITEMTHREE;
        }
    }
    /**
     * 第一个布局的Holder,要继承自RecyclerView.ViewHolder,这里你可以绑定控件
     */
    class OneItemHolder extends RecyclerView.ViewHolder {


        public OneItemHolder(@NonNull View itemView) {
            super(itemView);

        }
    }

    /**
     * 第二个布局的Holder,要继承自RecyclerView.ViewHolder,这里你可以绑定控件
     */
    class TwoItemHolder extends RecyclerView.ViewHolder {

        public TwoItemHolder(@NonNull View itemView) {
            super(itemView);
        }
    }

    /**
     * 第三个布局的Holder,要继承自RecyclerView.ViewHolder,这里你可以绑定控件
     */
    class ThreeItemHolder extends RecyclerView.ViewHolder {

        private  ConstraintLayout mitemthree;

        public ThreeItemHolder(@NonNull View itemView) {
            super(itemView);
            mitemthree = itemView.findViewById(R.id.item_three);
        }
    }
}

到这就结束了

若有什么不足或有什么建议、意见的小伙伴,欢迎你们在下方留言!

  • 1
    点赞
  • 4
    收藏
  • 打赏
    打赏
  • 1
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:终极编程指南 设计师:CSDN官方博客 返回首页
评论 1

打赏作者

小胖亭

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值