• <Android基础>(三) UI开发 Part 3 RecyclerView


    RecyclerView

    1)RecyclerView的基本用法

    2)横向滚动和瀑布流滚动

    3)注册点击事件

    3.6 强大的滚动控件 RecyclerView

    ListView缺点:

    1.不使用技巧优化,ListView效率很差。

    2.扩展性能不够好,只能实现数据纵向滚动。

    3.6.1 RecyclerView的基本用法

    1.RecylerView定义在了support库当中。

    首先需要在build.gradle中添加相应的依赖库,在dependencies闭包中添加内容。

        compile 'com.android.support:appcompat-v7:24.2.1'
        compile 'com.android.support:recyclerview-v7:24.2.1'
        testCompile 'junit:junit:4.12'

    2.修改main_layout.xml中的代码

    在布局中加入RecylerView。由于RecyclerView并不是内置在系统SDK中的,所以需要把完整的路径写出来。

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
    
    </LinearLayout>

    3.创建Hero类和hero_item.xml(同ListView)

    public class Hero {
        private String name;
        private int imageId;
        public Hero(String name, int imageId){
            this.name = name;
            this.imageId = imageId;
        }
    
        public String getName() {
            return name;
        }
    
        public int getImageId() {
            return imageId;
        }
    }
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <ImageView
            android:id="@+id/hero_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/hero_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="10dp"/>
    </LinearLayout>

    4.新建HeroAdapter类,让这个适配器继承自RecyclerView.Adapter,并将泛型指定为HeroAdapter.ViewHolder,ViewHolder是在HeroAdapter中定义的一个内部类。

    public class HeroAdapter extends RecyclerView.Adapter<HeroAdapter.ViewHolder>{
    
        private List<Hero> mHeroList;
    
        static class ViewHolder extends RecyclerView.ViewHolder{
            ImageView heroImage;
            TextView heroName;
    
            public ViewHolder(View view){
                super(view);
                heroImage = (ImageView)view.findViewById(R.id.hero_image);
                heroName = (TextView)view.findViewById(R.id.hero_name);
            }
        }
    
        public HeroAdapter(List<Hero> heroList){
            mHeroList = heroList;
        }
    
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.hero_item, parent, false);
            ViewHolder holder = new ViewHolder(view);
            return holder;
        }
    
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            Hero hero = mHeroList.get(position);
            holder.heroImage.setImageResource(hero.getImageId());
            holder.heroName.setText(hero.getName());
        }
    
        @Override
        public int getItemCount() {
            return mHeroList.size();
        }
    }

    首先定义一个内部类ViewHolder,ViewHolder要继承自RecylcerView.ViewHolder。ViewHolder的构造函数中传入的View参数,通常为RecylclerView子项的最外层布局。可以通过findVIewById()方法来获取到布局中的ImageView和TextView的实例。

    HeroAdapter中的构造函数,将要展示的数据源传进来,并赋值给一个全局变量mHeroList,后续操作都在该数据源的基础上进行。

    由于HeroAdapter继承了RecyclerView.Adapter,那么就必须重写onCreatViewHolder(),onBindViewHolder()和getItemCount()三个方法。

    onCreatViewHolder()方法用于创建ViewHolder实例,在这个方法中将hero_item布局传进来,然后创建一个VIewHolder实例,将加载出来的布局传入到构造函数中,最后将ViewHolder实例返回。

     @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.hero_item, parent, false);
            ViewHolder holder = new ViewHolder(view);
            return holder;
        }

    onBindViewHolder()方法是用于对RecyclerView子项的数据进行赋值,会在每个子项被滚动到屏幕内的时候执行,通过position参数得到当前项的Hero实例,然后再将数据置入ViewHolder的ImageView和TextView。

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            Hero hero = mHeroList.get(position);
            holder.heroImage.setImageResource(hero.getImageId());
            holder.heroName.setText(hero.getName());
        }

    getItemCount()方法用于告诉RecycleView一共有多少子项,直接返回数据源的长度。

     @Override
        public int getItemCount() {
            return mHeroList.size();
        }

    5.修改MainActivity中的代码

    public class MainActivity extends AppCompatActivity {
    
        private List<Hero> heroList = new ArrayList<Hero>();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
    
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main_layout);
            initHeros();
            RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
            LinearLayoutManager layoutManager = new LinearLayoutManager(this);
            recyclerView.setLayoutManager(layoutManager);
            HeroAdapter adapter = new HeroAdapter(heroList);
            recyclerView.setAdapter(adapter);
        }
    
        private void initHeros(){
            for(int i = 0 ; i < 2 ; i ++){
                Hero gareen = new Hero("gareen", R.drawable.gareen);
                heroList.add(gareen);
                Hero annie = new Hero("annie", R.drawable.annie);
                heroList.add(annie);
                Hero shana = new Hero("shana", R.drawable.shana);
                heroList.add(shana);
                Hero teemo = new Hero("teemo", R.drawable.teemo);
                heroList.add(teemo);
            }
        }
    }

    用同样的initHeros()方法,初始化所有英雄数据。

    在onCreat()方法中先获取到RecyclerView的实例,然后创建一个LinearLayoutManager对象,并设置到RecyclerView当中。LayoutManager用于指定RecyclerView的布局方式,这里使用LinearLayoutManager为线性布局。然后创建HeroAdapter的实例,并将英雄数据传入HeroAdapter的构造函数中,最后调用RecycleView的setAdapter()方法来完成适配器设置,完成RecyclerView和数据之间的关联。

    3.6.2 实现横向滚动和瀑布流滚动

    1.横向滚动

    a.首先对hero_item的布局进行修改,目前布局里的元素水平放置的,即为horizontal,把元素改为垂直放置,宽度设为100dp

    ImageView和TextView都设置为在布局中水平居中,并使用layout_marginTop让文字和图片之间保持一定距离

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="100dp"
        android:layout_height="wrap_content">
    
        <ImageView
            android:id="@+id/hero_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            />
    
        <TextView
            android:id="@+id/hero_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginLeft="10dp"/>
    </LinearLayout>

     b.修改MainActivity中的代码

    @Override
        protected void onCreate(Bundle savedInstanceState) {
    
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main_layout);
            initHeros();
            RecyclerView recyclerView = (RecyclerView) f 
            indViewById(R.id.recycler_view);
            LinearLayoutManager layoutManager = new LinearLayoutManager(this);
            layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
            recyclerView.setLayoutManager(layoutManager);
            HeroAdapter adapter = new HeroAdapter(heroList);
            recyclerView.setAdapter(adapter);
        }

    加一句

    layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);

    调用LinearLayoutManager的setOri()方法来设置布局的排列方向,默认为纵向,传入LinearLayoutManger.HORIZEONTAL表示让布局横向排列。

    运行程序:

    原因:ListView的布局排列是由自身去管理的,而RecylerView则由LayoutManager管理,LayoutManager中制定了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能定制出各种不同的排列方式的布局。

    2.瀑布流布局

    a.修改hero_item.xml中的布局,宽度由列数自动匹配而不是一个固定值,用layout_margin来让子项之间互留一点间距,TextView的对齐属性改为了左对齐,因为待会会将文字的长度边长,居中显示很奇怪

    <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_margin="5dp">
    
        <ImageView
            android:id="@+id/hero_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            />
    
        <TextView
            android:id="@+id/hero_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left"
            android:layout_marginLeft="10dp"/>
    </LinearLayout>

    b.修改MainActivit.java中的代码

    在onCreat()方法中创建了一个StaggeredGridLayoutManager的实例,构造函数接受两个参数,第一个参数用于指定布局的列数,第二个参数用于制定布局的排列方向,传入StaggeredGridLayoutManager.VERTICAL表示会让布局纵向排列。

    在getRandomLengthName()方法中得到每个英雄名字都长短不一,显示出来的效果更明显

    public class MainActivity extends AppCompatActivity {
    
        private List<Hero> heroList = new ArrayList<Hero>();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
    
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main_layout);
            initHeros();
            RecyclerView recyclerView = (RecyclerView)     
            findViewById(R.id.recycler_view);
            StaggeredGridLayoutManager layoutManager = new       
            StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
            recyclerView.setLayoutManager(layoutManager);
            HeroAdapter adapter = new HeroAdapter(heroList);
            recyclerView.setAdapter(adapter);
        }
    
        private void initHeros(){
            for(int i = 0 ; i < 3 ; i ++){
                Hero gareen = new Hero(getRandomLengthName("gareen"), R.drawable.gareen);
                heroList.add(gareen);
                Hero annie = new Hero(getRandomLengthName("annie"), R.drawable.annie);
                heroList.add(annie);
                Hero shana = new Hero(getRandomLengthName("shana"), R.drawable.shana);
                heroList.add(shana);
                Hero teemo = new Hero(getRandomLengthName("teemo"), R.drawable.teemo);
                heroList.add(teemo);
            }
        }
    
        private String getRandomLengthName(String name){
            Random random = new Random();
            int length = random.nextInt(20) + 1 ;
            StringBuilder res = new StringBuilder();
            for(int i = 0 ; i < length ; i ++){
                res.append(name);
            }
            return res.toString();
        }
    }        

    运行程序

    3.6.3 RecycleView的点击事件

    ListView中的setOnClickListener()方法注册的是子项的点击事件,如果是点击子项里的按钮,则实现起来很麻烦。

    RecyclerView摒弃了子项点击事件的监听器,所有监听事件都由具体的View来注册。

    在RecyclerView中注册点击事件,修改MainActivity中的代码

    public class HeroAdapter extends RecyclerView.Adapter<HeroAdapter.ViewHolder>{
    
        private List<Hero> mHeroList;
    
        static class ViewHolder extends RecyclerView.ViewHolder{
            View heroView;
            ImageView heroImage;
            TextView heroName;
    
            public ViewHolder(View view){
                super(view);
                heroView = view;
                heroImage = (ImageView)view.findViewById(R.id.hero_image);
                heroName = (TextView)view.findViewById(R.id.hero_name);
            }
        }
    
        public HeroAdapter(List<Hero> heroList){
            mHeroList = heroList;
        }
    
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.hero_item, parent, false);
    
            final ViewHolder holder = new ViewHolder(view);
            holder.heroView.setOnClickListener(new View.OnClickListener(){
                @Override
                public void onClick(View v) {
                    int position = holder.getAdapterPosition();
                    Hero hero = mHeroList.get(position);
                    Toast.makeText(v.getContext() , "You clicked view " + hero.getName(), Toast.LENGTH_SHORT ).show();
                }
            });
            holder.heroImage.setOnClickListener(new View.OnClickListener(){
                @Override
                public void onClick(View v) {
                    int position = holder.getAdapterPosition();
                    Hero hero = mHeroList.get(position);
                    Toast.makeText(v.getContext() , "You clicked image " + hero.getName(), Toast.LENGTH_SHORT).show();
                }
            });
    
            return holder;
        }
    
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            Hero hero = mHeroList.get(position);
            holder.heroImage.setImageResource(hero.getImageId());
            holder.heroName.setText(hero.getName());
        }
    
        @Override
        public int getItemCount() {
            return mHeroList.size();
        }
    }

    在ViewHolder中添加了heroView变量用来保存子项最外层布局的实例,然后在onCreatViewHolder()方法中注册事件。

    运行程序:

    点击图片部分:

    显示点击了图片

    点击图片下的TextView部分:由于TextView并没有注册时间,所以该事件会被子项最外层布局捕捉到。

     最后一点小小的总结:

    1.线性布局中orientation属性默认为horizontal(水平方向)

    2.android:gravity用于指定文字在控件中的对齐方式,android:layout_gravity用于指定控件在布局中的对齐方式。

    3.将控件宽度指定为0dp,用android:layout_weight来决定控件在屏幕的比例宽度

    <EditText
         android:id="@+id/input_message"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_weight="1" />
    <Button
         android:id="@+id/button"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_weight="1"
         android:text="Button"/>

    表示EditText和Button以1:1的比例平分屏幕,以此类推。

    4.动态加载布局的LayoutInflater方法,LayoutInflater的from()方法可以构建出一个LayouInflater对象,然后调用inflate()方法就可以加载一个布局文件。

    inflate()方法接受两个参数,第一个参数要加载的布局文件的id,第二个参数是给加载好的布局再添加一个父布局。

    接收三个参数,第三个为boolean型,false表示只让在父布局中声明的layout属性生效,但不会为View添加父布局。

    ListView中的标准写法为:

    View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
  • 相关阅读:
    String 对象-->indexOf() 方法
    String 对象-->大小比较
    String 对象-->判断是否相等
    String 对象-->toUpperCase() 方法
    String 对象-->toLowerCase() 方法
    String 对象-->fromCharCode() 方法
    String 对象-->charCodeAt() 方法
    从零开始学 Web 之 CSS3(六)动画animation,Web字体
    从零开始学 Web 之 CSS3(五)transform
    从零开始学 Web 之 CSS3(四)边框图片,过渡
  • 原文地址:https://www.cnblogs.com/HarSong13/p/10661095.html
Copyright © 2020-2023  润新知