RecyclerView 万能适配在文章末尾

同一个app在平板与手机上的显示,因为屏幕大小的区别,可能需要显示为不同的样式。如下图所示

android 设备双屏 android双屏同显_android

平板样式

android 设备双屏 android双屏同显_Android_02

手机样式

在平板状态下,于同一个页面显示两个部分,在手机中则显示在不同页面。

此处采用fragment 来实现。分为几个步骤。

1、首先判断设备是手机还是平板,此处使用官方提供的方法

/**
 * 返回 平板 true
 * 手机 false
 */
private fun isIPad():Boolean{
    return resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK >= Configuration.SCREENLAYOUT_SIZE_LARGE
}

返回值为true时表示当前设备时平板,返回false时表明当前设备时手机。

2、新建资源文件夹 layout-large

系统会自动判断当前的设备情况来加载layout或者layout-large中的资源布局文件,设备是手机时加载的是 layout文件夹下的布局文件,平板时则加载 layout-large 文件夹下的资源文件。

2.1 首先我们新建 两个fragment,用于布局需要

fragment_list 用于列表项加载:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

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

    </android.support.v7.widget.RecyclerView>

</LinearLayout>

fragment_detail 用于详情加载

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
            android:id="@+id/tv_detail_title"
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:gravity="center"
            android:text="555"/>

    <ImageView
            android:id="@+id/iv_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitCenter"
            android:src="@drawable/test_01"/>

</LinearLayout>

此处详情页面简化用于实现效果即可

创建 ListFragment 以及 DetailFragment 备用,其中 ListFragment  对应布局 fragment_list ,DetailFragment  对应布局 fragment_detail 。

由于 ListFragment 的代码相对复杂,在后面给出,此处先给出详情页 DetailFragment 代码

class DetailFragment :Fragment(){

    private var fview:View?=null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        if(fview==null){
            fview= inflater.inflate(R.layout.fragment_detail,null)
        }
        val parent=fview?.parent
        if(parent!=null){
            (parent as ViewGroup).removeView(fview)
        }
        return fview
    }

    /**
     * 开放数据更新接口 
      */
    fun setValueData(bean:Testbean){
        fview?.tv_detail_title!!.text=bean.title
        fview?.iv_image!!.setImageResource(bean.image)
    }

}

 

2.2 创建activity的布局文件

在 layout 下新建 布局文件 activity_test.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <fragment
            android:id="@+id/fragment_list"
            android:name="com.admin.testproject.fragment.ListFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

</LinearLayout>

以及activity_detail

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:id="@+id/frame_content"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">


</FrameLayout>

然后再 在 layout-large 文件夹下创建 activity_test.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <fragment
            android:id="@+id/fragment_list"
            android:name="com.admin.testproject.fragment.ListFragment"
            android:layout_width="0dp"
            android:layout_weight="1.0"
            android:layout_height="match_parent"/>
    <!-- 两个fragment 之间加一条分割线  -->
    <View
            android:layout_width="1dp"
            android:layout_height="match_parent"
            android:background="#ff0000"/>

    <FrameLayout
            android:id="@+id/fragment_content"
            android:layout_width="0dp"
            android:layout_weight="2.0"
            android:layout_height="match_parent">

    </FrameLayout>

</LinearLayout>

资源文件夹如下图

android 设备双屏 android双屏同显_android_03

2.3 处理相关的设置

首先创建 BaseActivity 并设置公共方法,用于判断设备情况以及根据情况设置 设备的横竖屏

abstract class BaseActivity :AppCompatActivity(){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        requestedOrientation = if(isIPad()){
            Log.e("***","isPad")
            ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
        }else{
            Log.e("***","isPhone")
            ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
        }
        setContentView(bindLayout())
        bindData()
        bindEvent()
    }

    abstract fun bindLayout():Int

    abstract fun bindData()

    abstract fun bindEvent()

    /**
     * 返回 平板 true
     * 手机 false
     */
    private fun isIPad():Boolean{
        return resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK >= Configuration.SCREENLAYOUT_SIZE_LARGE
    }
}

然后所有的 activity都继承 BaseActivity 。

新建 TestActivity 加载布局文件 activity_test

class TestActivity:BaseActivity(){

    override fun bindLayout(): Int {
        return R.layout.activity_test
    }

    override fun bindData() {

    }

    override fun bindEvent() {
    }

}

我们在 ListFragment 处理 单双屏的判断问题。

在 两个 activity_test.xml 文件中,我们的布局文件是有差异的,平板加载的布局文件比手机加载的布局文件多了一个FragmeLayout,我们通过判断这个 FragmeLayout 是否存在来判断是否为双屏

android 设备双屏 android双屏同显_android_04

判断的方法为

isTwopage = activity!!.findViewById<FrameLayout>(R.id.fragment_content)!=null

isTwopage 为true时表示双屏(平板),false时为单屏(手机)

然后我们初始化ListFragment 的测试数据数据,ListFragment 的代码为

class ListFragment :Fragment(){

    private var fview:View?=null
    private var isTwopage=false
    private val titles= arrayOf("title 001","title 002","title 003","title 004","title 005")
    private val images= arrayOf(R.drawable.test_01,R.drawable.test_02,R.drawable.test_03,R.drawable.test_04,R.drawable.test_05)
    private val data=ArrayList<Testbean>()
    private var adapter:TestAdapter?=null
    private var detailFragment:DetailFragment?=null

    @SuppressLint("InflateParams")
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        if(fview==null){
            fview= inflater.inflate(R.layout.fragment_list,null)
            initData()
        }
        val parent=fview?.parent
        if(parent!=null){
            (parent as ViewGroup).removeView(fview)
        }
        return fview
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        isTwopage = activity!!.findViewById<FrameLayout>(R.id.fragment_content)!=null
        if(isTwopage){
            //如果是双屏,加载 DetailFragment
            detailFragment= DetailFragment()
            activity!!.supportFragmentManager.beginTransaction().replace(R.id.fragment_content,detailFragment!!).commit()
        }
        Log.e("****","$isTwopage")
    }


    private fun initData(){
        for(i in 0 until titles.size){
            data.add(Testbean(titles[i],images[i]))
        }
        adapter= TestAdapter(activity!!)
        fview!!.rlv_list.layoutManager=LinearLayoutManager(activity!!)
        fview!!.rlv_list.adapter=adapter
        adapter?.upData(data)
        adapter?.onItemClick= object : OnItemClickListener<Testbean> {
            override fun onItemClick(item: Testbean) {
                if(isTwopage){
                    //双屏直接更新数据
                    click(item)
                }else{
                    //单屏直接跳转到详情activity
                    val intent=Intent(activity!!,NewDetailActivity::class.java)
                    val bundle=Bundle()
                    bundle.putSerializable("bean",item)
                    intent.putExtras(bundle)
                    startActivity(intent)
                }
            }
        }
    }

    override fun onStart() {
        super.onStart()
        if(isTwopage){
            //双屏初始化详情页数据
            click(data[0])
        }
    }

    fun click(item: Testbean){
        detailFragment?.setValueData(item)
    }

}

Testbean 的代码

class Testbean ( var title:String,var image:Int):Serializable{

}

DetailActivity的代码

class DetailActivity :BaseActivity(){


    val detailFragmrnt=DetailFragment()
    val handler=Handler()

    override fun bindLayout(): Int {
        return R.layout.activity_new_detail
    }

    override fun bindData() {
        supportFragmentManager.beginTransaction().replace(R.id.frame_content,detailFragmrnt).commit()
    }

    override fun bindEvent() {
        val title=intent.getSerializableExtra("bean") as Testbean
        handler.postDelayed({
            detailFragmrnt.setValueData(title)
        },10)
    }

}

附上代码运行的效果,比开头的详细一点:

android 设备双屏 android双屏同显_android_05

手机效果

平板效果与开头的一致,不过还是放一下

android 设备双屏 android双屏同显_平板与手机适配_06

测试的图片就不放了。下面给出的代码是 RecyclerView 的万能适配 器

RecycleBaseAdapter

public abstract class RecycleBaseAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> {

    private List<T> data;
    public Context context;
    public OnItemClickListener onItemClick;
    public abstract int getItemLayout(int position);

    public void upData(List<T> data) {
        this.data = data;
        notifyDataSetChanged();
    }

    public RecycleBaseAdapter(Context context){
        this.context=context;
    }

    @Override
    public int getItemViewType(int position) {
        //可以通过修改此处的返回值,来改变 需要加载的 页面类型,
        // 然后在getItemLayout中判断相同的值的时候返回所需的 布局ID
        return position;
    }

    @NonNull
    @Override
    public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        return BaseViewHolder.getHolder(viewGroup,getItemLayout(i));
    }

    @Override
    public void onBindViewHolder(@NonNull BaseViewHolder viewHolder, int i) {
        if(viewHolder!=null && data.get(i)!=null) {
            initData(viewHolder, data.get(i), i);
        }
    }

    public abstract void initData(BaseViewHolder viewHolder, T item, int position);

    @Override
    public int getItemCount() {
        return data==null?0:data.size();
    }

}

BaseViewHolder

public class BaseViewHolder extends RecyclerView.ViewHolder{
    private SparseArray<View> mviews;
    private View mContentView;
    private BaseViewHolder(@NonNull View itemView) {
        super(itemView);
        mContentView=itemView;
        mviews=new SparseArray<View>();
    }

    static BaseViewHolder getHolder(ViewGroup parent, int layoutId){
        View view= LayoutInflater.from(parent.getContext()).inflate(layoutId,parent,false);
        return new BaseViewHolder(view);
    }

    public <T extends View> T getView(int viewId){
        View view=mviews.get(viewId);
        if(view==null){
            view=mContentView.findViewById(viewId);
            mviews.put(viewId,view);
        }
        return (T)view;
    }
}

以上是 RecyclerView  的通用适配代码,下面是本文中的TestAdapter代码

class TestAdapter (context: Context):RecycleBaseAdapter<Testbean>(context){

    override fun getItemLayout(position: Int): Int {
        return R.layout.layout_recy_item
    }

    override fun initData(viewHolder: BaseViewHolder?, item: Testbean?, position: Int) {
        viewHolder?.getView<TextView>(R.id.tv_item)?.text=item?.title
        viewHolder?.getView<ImageView>(R.id.iv_title)?.setImageResource(item?.image!!)
        viewHolder?.getView<LinearLayout>(R.id.linear_item)?.setOnClickListener {
            onItemClick?.onItemClick(item)
        }
    }

}

使用起来就是如此简单。

注:中代码混合了 java 与 kotlin