2020-10-19

语言: CN / TW / HK

AS新手总结:TabLayout与ViewPager联动实现滑页)

   最近在学完安卓开发的UI部分,想利用现学知识练练手,于是做了个仿今日头条
可以左右滑页的界面。
   基本思想:导航栏使用TabLayout作为页面切换指示器,新闻栏目使用ViewPager
嵌套RecyclerView的方式实现左右滑页功能,并将TabLayout和ViewPager联动。

首先我们设计 activity_main.xml 布局文件,代码如下:

<?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"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 实现标题和导航栏-->
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/mine_background">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <ImageView
                android:id="@+id/menuImage"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_gravity="center"
                android:layout_marginLeft="5dp"
                android:background="@drawable/menu"
                android:layout_margin="5dp" />

            <TextView
                android:id="@+id/titleView"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_weight="1"
                android:gravity="center"
                android:text="My Top News"
                android:textColor="#fff"
                android:textStyle = "bold"
                android:textSize="24sp"/>
        </LinearLayout>

        <!--- TabLayout控件使用 -->
        <com.google.android.material.tabs.TabLayout
            android:id="@+id/menuTab"
            android:layout_marginTop="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:tabSelectedTextColor="#51F3E0"
            app:tabIndicatorColor="#51F3E0"
            app:tabMode="scrollable"
            app:tabTextAppearance="@style/TabLayoutTextStyle" />
    </LinearLayout>

    <!-- 插入ViewPager控件-->
    <androidx.viewpager.widget.ViewPager
        android:id="@+id/my_viewPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

其中TabLayout用法可参考Tablayout使用全解,一篇就够了

接下来, 我们需要为ViewPager设计一个RecyclerView的界面,就类似于我们写ListView或者RecyclerView的时候,需要为他们设计一个item(子项)布局,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/recyclerView_1"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

</androidx.recyclerview.widget.RecyclerView>

这里需要注意的是,我们这里的布局格式不再选择LinearLayout, 而是直接使用androidx.recyclerview.widget.RecyclerView

接着,我们同样需要为RecyclerView设计一个子项布局,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="10dp">
	<!-- 使用View为每个新闻栏目添加边框 -->
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#FACECE"/>

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content">
	
		<!-- 左侧用于显示新闻的主图 -->
       <ImageView
           android:id="@+id/news_image"
           android:layout_width="80dp"
           android:layout_height="80dp"
           android:layout_marginLeft="3dp"
           android:scaleType="centerCrop" />
		
		<!-- 右侧用于显示新闻摘要 -->
       <TextView
           android:id="@+id/news_introduction"
           android:layout_width="0dp"
           android:layout_height="wrap_content"
           android:layout_weight="1"
           android:layout_gravity="center"
           android:layout_marginLeft="10dp"
           android:maxLines="2"
           android:ellipsize="end"
           android:textSize="20sp"
           android:textColor="#000" />
   </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#FACECE"/>


</LinearLayout>

到这里,我们就完成了所有布局文件的设计了,接下来我们需要来编写RecyclerView适配器和ViewPager适配器的代码。

先编写RecyclerView适配器的代码,首先我们需要创建一个新闻类,代码如下:

package com.example.mytopnews20;

public class NewsDirection {
   
   
    private String introduction; // 新闻梗概
    private int imageId; // 新闻栏图片
    private String uriId; // 新闻的地址Id, 还没实现调用权限,所以uriId暂时用不到

    public NewsDirection(String introduction, int imageId, String uriId) {
   
   
        this.introduction = introduction;
        this.imageId = imageId;
        this.uriId = uriId;
    }

    public String getIntroduction() {
   
   
        return introduction;
    }

    public int getImageId() {
   
   
        return imageId;
    }

    public String getUriId() {
   
   
        return uriId;
    }
}

然后我们为RecyclerView编写一个适配器,代码如下:

package com.example.mytopnews20;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

/** 这里是RecyclerVieww适配器的标准写法,也即必须重写以下三个方法
 *  这里我们自定义一个适配器继承自RecyclerView.Adapter, 并且我们需要将泛型指定为NewsAdapter.ViewHolder
 *  其中ViewHolder是NewsAdapter里面的一个内部类
 */
public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
   
   

    private List<NewsDirection> newsDirections;

    // 这里我们定义一个内部类ViewHolder,其作用是在RecyclerView滚动时设置值
    static class  ViewHolder extends RecyclerView.ViewHolder {
   
   
        ImageView newsImage;
        TextView newsIntroduction;
        public ViewHolder(View view) {
   
   
            super(view);
            newsImage = view.findViewById(R.id.news_image);
            newsIntroduction = view.findViewById(R.id.news_introduction);
        }
    }

    // 构造函数,用于传入数据源
    public NewsAdapter(List<NewsDirection> newsDirections) {
   
   
        this.newsDirections = newsDirections;
    }

    // 以下三个函数一般都需要重写
    // 其中onCreateViewHolder方法用于创建ViewHolder实例, 在这个方法当中
    // 我们需要把RecyclerView的子项布局加载进来,并返回ViewHolder的实例
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
   
   
        View view = LayoutInflater.from(parent.getContext()).
                inflate(R.layout.news_item, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    // onBindViewHolder方法用于对RecyclerView子项的数据进行赋值, 会在每个子项
    // 被滚动到屏幕内的时候执行,我们通过position参数获得当前的NewsDirection实例
    // 然后将数据设置到ViewHolder的newsImage和newsIntroduction中即可
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
   
   
        NewsDirection newsDirection = newsDirections.get(position);
        holder.newsImage.setImageResource(newsDirection.getImageId());
        holder.newsIntroduction.setText(newsDirection.getIntroduction());
    }

    //  getItemCount方法用于告诉RecyclerView一共有几个子项,这里我们直接返回数据源的长度
    @Override
    public int getItemCount() {
   
   
        return newsDirections.size();
    }
}

一般来说,RecyclerView的适配器编写都比较固定,这里总结一下:首先需要编写一个你需要展示的信息的类,并创建一个链表作为数据源。接着我们需要编写NewsAdapter类,也即为我们的需要展示的信息量身定做一个适配器,在类中,我们需要重写三个方法,具体请看代码注释。

接着,我们开始编写ViewPager的适配器,代码如下:

package com.example.mytopnews20;

import android.view.View;
import android.view.ViewGroup;

import androidx.viewpager.widget.PagerAdapter;

import java.util.List;

public class ViewPagerAdapter extends PagerAdapter {
   
   
    private List<View> viewList;
    public ViewPagerAdapter(List<View> views) {
   
   
        super();
        this.viewList = views;
    }

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

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
   
   
        container.addView(viewList.get(position));
        return viewList.get(position);
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
   
   
        container.removeView(viewList.get(position));
    }

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
   
   
        return (arg0 == arg1);
    }
}

ViewPager适配器的编写方法和RecyclerView的编写方法类似,这里不再赘余,详情可参考Android:ViewPager适配器PagerAdapter的使用

到此,可以说前期的准备已经全部完成了,接着就可以编写主函数了,注意:我们在主函数里面实现了TabLayout和ViewPager的联动,代码如下

package com.example.mytopnews20;

import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;

import com.google.android.material.tabs.TabLayout;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
   
   

    private List<List<NewsDirection>> listArray = new ArrayList<>(6); // 定义一个二维数组,存放六个界面的新闻内容
    private List<View> viewList = new ArrayList<>(); // 用于存放六个页面的view
    private TabLayout tabLayout; // 使用tabLayout设置导航栏, 实现带有标志的导航效果
    private ViewPager viewPager; // 使用ViewPager存放六个view, 实现左右滑动翻页, 并和tabLayout联动

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

        // 将欢迎界面系统自带的标题栏隐藏
        ActionBar actionBar = getSupportActionBar();
        if(actionBar != null) {
   
   
            actionBar.hide();
        }
        initMenuTabs(); // 初始化导航栏tabLayout内容
        initNewsInfo(); // 初始化新闻信息
        initViewPager(); // 初始化viewPager页面信息
        BindTabAndPager(); // 绑定tabLayout和viewPager,实现联动
    }

    private void initMenuTabs() {
   
   
        tabLayout = this.findViewById(R.id.menuTab);
        // 使用addTab函数添加tab, 其中,使用newTab创建一个新的tab,使用setText设置文字,使用setIcon设置图标
        tabLayout.addTab(tabLayout.newTab().setText("Entertainment").setIcon(R.drawable.entertainment));
        tabLayout.addTab(tabLayout.newTab().setText("Learning").setIcon(R.drawable.learning));
        tabLayout.addTab(tabLayout.newTab().setText("SchoolNews").setIcon(R.drawable.school));
        tabLayout.addTab(tabLayout.newTab().setText("Sports").setIcon(R.drawable.sports));
        tabLayout.addTab(tabLayout.newTab().setText("Shopping").setIcon(R.drawable.shoping));
        tabLayout.addTab(tabLayout.newTab().setText("Others").setIcon(R.drawable.others));
    }

    private void initNewsInfo() {
   
   
        for (int i = 0; i < 6; i++) {
   
   
            NewsDirection news = new NewsDirection("news" + i, R.drawable.newsphoto, null);
            List<NewsDirection> list = new ArrayList<>();
            list.add(news);
            listArray.add(list);
        }
    }

    private void initViewPager() {
   
   

        /** LayoutInflater是一个用于解析xml的类,它的作用类似于findViewById,
         *不同的是,LayoutInflater是用来找res/layout下的xml布局文件,并且实例化,而findViewById是
         * 查找xml布局文件下的具体widget控件,如Button,TextView等。
         * 1. 对于一个没有载入或者想要动态载入的界面,都需要使用LayoutInflater.inflate()来载入
         * 2. 对于一个已经载入的界面,就可以使用Activity.findViewById()方法来获得其中的界面元素
         */

        LayoutInflater inflater = getLayoutInflater(); // 实例化inflater
        // 这里注意:对于不同的recyclerView需要使用不同的layoutManager。 因此定义一个LinearLayoutManager数组
        LinearLayoutManager layoutManagerArray[] = new LinearLayoutManager[6];
        RecyclerView recyclerViewArray[] = new RecyclerView[6];
        View viewArray[] = new View[6];
        // NewsAdapter用于向recyclerView传输信息
        NewsAdapter[] adapterArray = new NewsAdapter[6];

        // 这里实现每个recyclerView的初始化
        for (int i = 0; i < 6; i++) {
   
   
            layoutManagerArray[i] = new LinearLayoutManager(this);
            viewArray[i] = inflater.inflate(R.layout.recyclerview, null);
            recyclerViewArray[i] = viewArray[i].findViewById(R.id.recyclerView_1);
            recyclerViewArray[i].setLayoutManager(layoutManagerArray[i]);
            adapterArray[i] = new NewsAdapter(listArray.get(i));
            recyclerViewArray[i].setAdapter(adapterArray[i]);
            viewList.add(recyclerViewArray[i]);
        }
        // 将recyclerView嵌套到ViewPager里面
        viewPager = this.findViewById(R.id.my_viewPager);
        ViewPagerAdapter myAdapter = new ViewPagerAdapter(viewList);
        viewPager.setAdapter(myAdapter);
    }

    private void BindTabAndPager() {
   
   
        // 这个地方必须重新加载View,否则系统会找不到tabLayout和viewPager
        tabLayout = this.findViewById(R.id.menuTab);
        viewPager = this.findViewById(R.id.my_viewPager);
        // Viewpager的监听(这个接听是为TabLayout专门设计的)
        viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
        //TabLayout的监听
        tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
   
   
            // 被选中的时候
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
   
   
                int position = tab.getPosition();
                viewPager.setCurrentItem(position);
            }
            // 没有被选中的时候
            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
   
   

            }
            // 重现被选中的时候
            @Override
            public void onTabReselected(TabLayout.Tab tab) {
   
   

            }
        });
    }
}

最后,我们看一下效果

在这里插入图片描述
以上为个人初学实践总结,有很多不足之处,还请各位大佬不吝赐教,批评指正。

分享到: