Material Design最佳体验(2): 使用RecyclerView、CardView、SwipeRefreshLayout实现下拉刷新列表

               Material Design最佳体验(2):

  使用RecyclerView、CardView、SwipeRefreshLayout实现下拉刷新列表

 

  作者:

   蒋东国

  时间:

    2017年03月11日 星期六    

  应用来源:

    路痴宝v1.2 (三星C9 Pro)                         

  博客地址:

   http://blog.csdn.net/andrexpert/article/details/61419698   

      

       情景再现“Material Design是Google在2014年的I/O大会提出的一套全新的界面设计语言,它包含了视觉、运动、互动效果等特性,其宗旨就是解决Android平台界面风格不统一的问题。虽然Material Design是一套界面设计语言,但为了方便APP开发,Google提供了一个Design Support库,这个库对一些常见的控件和效果进行了封装,使开发工程师在在不了解Material Design的情况下能够将自己的应用Material化。”

      

       上一篇博文主要介绍了 Meterial Design最佳体验(1):使用Toolbar,CoordinatorLayout, AppBarLayout等实现精美标题栏,通过该文我们应该对MaterialDesign控件有了初步的了解。今天将使用RecyclerView、CardView、SwipeRefreshLayout控件继续介绍一个具有下拉刷新功能的高度Material化的列表,也是对上一篇博文未介绍的相关内容的补充。当然,首先我们通过一张gif图片来“观望”下本节内容的最终效果,嗯,眼见为实:



      RecyclerView是support-v7库提供的一个强大的滚动控件,它可以说是一个增强版的ListView,不仅可以轻松实现和ListView同样的效果,还优化了ListView中存在的各种不足,比如ViewHolder视图缓存机制、水平列表、瀑布式列表等等。Android Studio开发环境中使用RecyclerView非常简单,只需要在build.gradle中添加相应的依赖库即可:

dependencies {
     compile 'com.android.support:appcompat-v7:24.+'
     //Material Design Support库
     compile 'com.android.support:design:24.2.1'
     // Cardview Support库
     compile 'com.android.support:cardview-v7:24.2.1'
     // RecyclerView Support库
    compile 'com.android.support:recyclerview-v7:24.2.1'
}

       RecyclerView的使用步骤与ListView类似,但与ListView自身管理布局排列不同的是,RecyclerView的布局排列是由RecyclerView.LayoutManager管理的,LayoutManager中制订了一套可扩展的布局排列接口,子类只要按照接口的规范来实现,就能实现各种不同排列方式的布局。另外,RecyclerView还提供了如下几个内部类ItemAnimator、ItemDecoration、OnScrollListener、OnItemTouchListener、Adapter、ViewHolder等,通过继承这些内部类可以实现列表项删减动画、列表项分割线风格、响应列表项滚动事件、响应列表项点击事件、RecyclerView所需的Adapter。Java层RecyclerView使用代码如下:

 RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.main_pic_list);
 // 设置布局方式为垂直方式, 
 LinearLayoutManager manager = new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,true);
 mRecyclerView.setLayoutManager(manager);
 //设置默认动画为item增减动画风格
 mRecyclerView.setItemAnimator(new DefaultItemAnimator());  
 //设置item分割线风格			
 mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration(){});
 //设置Adapter
 VideoInfoListAdapter mAdapter = new VideoInfoListAdapter();
 mAdapter.setAdapterData(getListAdapterData());
 mAdapter.setOnRecyclerClickListener(listener)
 mRecyclerView.setAdapter(mAdapter);
其中,RecyclerView的布局排列还可以是水平布局LinearLayoutManager .HORIZONTAL、网格布局GridLayoutManager、瀑布式布局StaggeredGridLayoutManager,它们的使用方法同LinearLayoutManager类似,具体API参见/support/v7/widget/RecyclerView.LayoutManager.html;RecyclerView的Adapter就与ListView的不一致了,它需要继承内部类RecyclerView.Adapter并实现一个RecyclerView.ViewHolder的子类,VideoIndoListAdapter.class完整代码如下

/**
 * list数据适配器
 * Created by jiangdongguo on 2017/3/8.
 * e-mail:luchibao2016@126.com
 * Blog:http://blog.csdn.net/andrexpert
 * MyApp:http://www.anzhi.com/soft_2729689.html (安智市场)
 */

public class VideoInfoListAdapter extends RecyclerView.Adapter<VideoInfoListAdapter.ViewHolder> {
    private Context mContext;
    private VideoThumbnailUtil mThumbnailUtil;
    private List<VideoInfoDetailBean> mVideoInfoBeans;
    private OnRcyclerClickListener listener;

    //点击事件对外回调接口
    public interface OnRcyclerClickListener{
        void onClicked(int itemPosition,View view);
        void onLongClicked(int itemPosition,View view);
    }

    public void setOnRecyclerClickListener(OnRcyclerClickListener listener){
        this.listener = listener;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        CardView mCardView;
        ImageView mIvVideoThumbnail;
        TextView mTvVideoTitle;
        TextView mTvVideoDuration;
        TextView mTvVideoSize;

        //缓存item所有子控件
        public ViewHolder(View itemView) {
            super(itemView);
            mCardView = (CardView) itemView.findViewById(R.id.item_video_cardview);
            mIvVideoThumbnail = (ImageView) itemView.findViewById(R.id.item_video_image);
            mTvVideoTitle = (TextView) itemView.findViewById(R.id.item_video_title);
            mTvVideoDuration = (TextView) itemView.findViewById(R.id.item_video_duration);
            mTvVideoSize = (TextView) itemView.findViewById(R.id.item_video_size);
        }
    }

    public void setAdapterData(List<VideoInfoDetailBean> mVideoInfoBeans) {
        this.mVideoInfoBeans = mVideoInfoBeans;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder,final int position) {
        //绑定数据到每个item,其中holder为缓存item视图实例
        VideoInfoDetailBean bean = mVideoInfoBeans.get(position);
        if (bean != null) {
            String[] mimeType = bean.getMimeType().split("/");
            final String videoPath = bean.getVideoPath();
            long duration = bean.getDuration();
            long size = bean.getVideoSize();
            holder.mTvVideoTitle.setText(bean.getVideoTitle() + "." + mimeType[mimeType.length - 1]);
            holder.mTvVideoDuration.setText("时长:" + getVideoDuration(duration));
            holder.mTvVideoSize.setText("大小:" + getVideoSize(size));
            //异步加载视频缩略图,设置tag保证映射关系
            holder.mIvVideoThumbnail.setImageResource(R.mipmap.video_empty_bg);
            holder.mIvVideoThumbnail.setTag(videoPath);
            mThumbnailUtil.loadImageThumbnail(videoPath, holder.mIvVideoThumbnail);

            //为item注册点击事件
            if(listener != null){
                holder.mCardView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        listener.onClicked(position,holder.mCardView);
                    }
                });

                holder.mCardView.setOnLongClickListener(new View.OnLongClickListener(){
                    @Override
                    public boolean onLongClick(View v) {
                        listener.onClicked(position,holder.mCardView);
                        return true;
                    }
                });
            }
        }
    }

    @Override
    public int getItemCount() {
        //列表item的个数
        return mVideoInfoBeans.size();
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //获得视频缩略图工具类实例
        mThumbnailUtil = new VideoThumbnailUtil();
        //获得item视图,并将其与ViewHolder进行绑定
        if (mContext == null) {
            mContext = parent.getContext();
        }
        View mView = LayoutInflater.from(mContext).inflate(R.layout.list_item_layout, null);
        return new ViewHolder(mView);
    }


    private String getVideoSize(long byteSize) {
        StringBuilder mBuilder = new StringBuilder();
        long mbSize = byteSize / 1024 / 1024;
        if (mbSize >= 1) {
            //大于1M,以MB为单位
            mBuilder.append(String.valueOf(mbSize));
            mBuilder.append(".");
            mBuilder.append(String.valueOf(byteSize / 1024 % 1024).charAt(0));
            mBuilder.append("M");
        } else {
            //小于1M,以KB为单位
            long kbSize = byteSize / 1024;
            mBuilder.append(String.valueOf(kbSize));
            mBuilder.append("kb");
        }
        return mBuilder.toString();
    }

    private String getVideoDuration(long duration) {
        StringBuilder mBuilder = new StringBuilder();
        long timeInSeconds = duration / 1000;
        int hour, min, sec;
        hour = (int) timeInSeconds / 3600;
        timeInSeconds = (int) timeInSeconds - (hour * 3600);
        min = (int) timeInSeconds / 60;
        timeInSeconds = timeInSeconds - (min * 60);
        sec = (int) timeInSeconds;
        if (hour > 0) {
            mBuilder.append(String.valueOf(hour));
            mBuilder.append("小时");
        }
        if (min > 0) {
            mBuilder.append(String.valueOf(min));
            mBuilder.append("分钟");
        }
        if (sec > 0) {
            mBuilder.append(String.valueOf(sec));
            mBuilder.append("秒");
        }
        return mBuilder.toString();
    }
}

分析:RecyclerView.Adapter继承于RecyclerView.Adapter<VideoInfoListAdapter.ViewHolder>,它需要覆写方法onCreateViewHolder、onBindViewHolder分别完成获取列表项视图所有子控件、为Item绑定相应数据,并实现一个继承RecyclerView.ViewHolder静态内部类ViewHolder。该内部静态内用于实现item视图缓存机制,同时作为RecyclerView.Adapter的传入参数。这里需要注意的是,在RecyclerView的Adapter内部还自定义了一个事件响应接口,用于访问者能够及时相应Item指定视图的点击事件。当然,如果仅仅是相应RecyclerView列表项点击事件,可以使用RecyclerView的OnItemTouchListener来监听。

       接下来,你或许注意到了,我们这个Demo不仅使用RecyclerView、CardView来展示列表数据,还具有下拉刷新列表来获取最新列表数据的功能,而实现这个功能控件就是SwipeRefreshLayout。SwipeRefreshLayout由support-v4提供具有Material特性的下拉刷新的核心类,它实现的过程相当简单,即我们只需要将想要实现下拉刷新的控件放置到SwipeRefreshLayout中,就可以迅速让这个控件支持下拉刷新。activity_main.xml完整代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayoutxmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
    android:layout_height="match_parent">
 
   <android.support.design.widget.AppBarLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content">
 
       <android.support.v7.widget.Toolbar
           android:id="@+id/main_toolbar"
            android:layout_width="match_parent"
           android:layout_height="?attr/actionBarSize"
           android:background="@color/colorPrimary"
           android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
           app:layout_scrollFlags="scroll|snap|enterAlways"
           app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
   </android.support.design.widget.AppBarLayout>
 
   <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/main_swipe_refresh_layout"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:layout_behavior="@string/appbar_scrolling_view_behavior">
 
       <android.support.v7.widget.RecyclerView
            android:id="@+id/main_pic_list"
           android:layout_width="match_parent"
           android:layout_height="match_parent"/>
   </android.support.v4.widget.SwipeRefreshLayout>
 
   <android.support.design.widget.FloatingActionButton
        android:id="@+id/main_delete_float_btn"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_gravity="bottom|end"
       android:layout_marginBottom="13dp"
       android:layout_marginRight="13dp"
        android:scaleType="centerCrop"
       android:src="@mipmap/ic_choose"
       app:elevation="10dp" />
</android.support.design.widget.CoordinatorLayout>

       从activity_main.xml代码可知,我们只需要在RecyclerView包裹一层SwipeRefreshLayout,那么RecyclerView就自动拥有了下拉刷新功能。需要注意的是,由于我们的AppBarLayout需要响应RecyclerView的滚动事件,还需要将app:layout_behavior属性移动至SwipeRefreshLayout。既然RecyclerView拥有了下拉刷新的功能,自然下一步就是如何去响应处理下拉刷新事件了,MainActivity.claa完整代码如下:

/**
 * 视频列表
 * Created by jiangdongguo on2017/3/8.
 * e-mail:luchibao2016@126.com
 * Blog:http://blog.csdn.net/andrexpert
 * MyApp:http://www.anzhi.com/soft_2729689.html(安智市场)
 */
public class MainActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private SwipeRefreshLayoutmSwiperefresh;
    private VideoInfoListAdaptermAdapter;
 
    privateVideoInfoListAdapter.OnRcyclerClickListener listener = newVideoInfoListAdapter.OnRcyclerClickListener() {
        @Override
        public void onClicked(intitemPosition, View view) {
            Snackbar.make(view,"删除数据", Snackbar.LENGTH_SHORT).setAction("取消", new View.OnClickListener() {
                @Override
                public voidonClick(View v) {
 
                }
            }).show();
        }
 
        @Override
        public voidonLongClicked(int itemPosition, View view) {
 
        }
    };
 
    @Override
    protected void onCreate(BundlesavedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
        initToolbar();
        initRefeshView();
        //悬浮按钮
        FloatingActionButtonmBtnDeleteData = (FloatingActionButton)findViewById(R.id.main_delete_float_btn);
        mBtnDeleteData.setOnClickListener(newView.OnClickListener() {
            @Override
            public void onClick(Viewview) {
            }
        });
    }
 
    private void initToolbar() {
        Toolbar mToolbar = (Toolbar)findViewById(R.id.main_toolbar);
        setSupportActionBar(mToolbar);
        ActionBar actionBar =getSupportActionBar();
        if (actionBar != null) {
           actionBar.setDisplayHomeAsUpEnabled(true);
           actionBar.setHomeButtonEnabled(true);
        }
    }
 
    private void initRefeshView() {
        //实例化RecyclerView
        mAdapter = newVideoInfoListAdapter();
       mAdapter.setAdapterData(getListAdapterData());
       mAdapter.setOnRecyclerClickListener(listener);
        mRecyclerView =(RecyclerView) findViewById(R.id.main_pic_list);
        LinearLayoutManager manager= new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false);
       mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setAdapter(mAdapter);
       mRecyclerView.setItemAnimator(new DefaultItemAnimator());       //设置Item动画
       mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL_LIST));
        //实例化SwipeRefreshLayout
        mSwiperefresh =(SwipeRefreshLayout) findViewById(R.id.main_swipe_refresh_layout);
       mSwiperefresh.setColorSchemeResources(R.color.colorPrimary);
       mSwiperefresh.setOnRefreshListener(newSwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh(){
                refreshVideoData();
            }
        });
    }
 
    private void refreshVideoData(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                   Thread.sleep(2000);
                } catch(InterruptedException e) {
                   e.printStackTrace();
                }
                runOnUiThread(newRunnable() {
                    @Override
                    public voidrun() {
                       mAdapter.setAdapterData(getListAdapterData());
                       mAdapter.notifyDataSetChanged();
                       mSwiperefresh.setRefreshing(false);
                       Toast.makeText(MainActivity.this,"刷新成功",Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }).start();
    }
 
    privateList<VideoInfoDetailBean> getListAdapterData() {
        List<VideoInfoDetailBean>mInfoBeans = new ArrayList<>();
        //遍历外部存储器所有视频文件,过滤时长为0的无效文件
        Cursor mCusor =getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null,null, null, null);
        while (mCusor.moveToNext()){
            long duration =mCusor.getLong(mCusor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION));
            if (duration == 0) {
                continue;
            }
            VideoInfoDetailBean bean= new VideoInfoDetailBean();
            bean.setDuration(duration);
           bean.setVideoTitle(mCusor.getString(mCusor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE)));
           bean.setVideoSize(mCusor.getLong(mCusor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)));
            bean.setMimeType(mCusor.getString(mCusor.getColumnIndexOrThrow(MediaStore.Video.Media.MIME_TYPE)));
           bean.setVideoPath(mCusor.getString(mCusor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)));
            mInfoBeans.add(bean);
        }
        return mInfoBeans;
    }
 
    @Override
    public booleanonCreateOptionsMenu(Menu menu) {
       getMenuInflater().inflate(R.menu.menu_toolbar, menu);
        return true;
    }
 
    @Override
    public booleanonOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        switch (id) {
            case R.id.menu_personal:
                break;
            case R.id.menu_add:
                break;
            case R.id.menu_more:
                break;
            default:
                break;
        }
        return true;
    }
 
}

    好了,写到这里,关于RecyclerView、CardView、SwipeRefreshLayout的学习基本就算结束了。最后,再稍微解释下获取列表数据相关知识和逻辑,这里主要使用getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,null,null, null, null)来获取内部存储器的所有视频格式文件,然后再封装一个VideoThumbnailUtil类实现列表视频缩略图的填充。在VideoThumbnailUtil中,视频缩略图由类ThumbnailUtils的相关方法来生成,然后使用异步任务和LruCache来缓存填充不同Item中的ImageView。VideoThumbnailUtil.class完整代码:

/**获取缩略图工具类
 * Created by jiangdongguo on 2017/3/11.
 * e-mail:luchibao2016@126.com
 * Blog:http://blog.csdn.net/andrexpert
 * MyApp:http://www.anzhi.com/soft_2729689.html (安智市场)
 */

public class VideoThumbnailUtil {
    private LruCache<String,Bitmap> mLruCache;

    public VideoThumbnailUtil(){
        //设备内存大小
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        //缓存大小
        int cacheSize = maxMemory / 8;
        //实例化LruCache,重写sizeof衡量每张图片的大小,返回图片的数量
        mLruCache = new LruCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap bm) {
                return bm.getByteCount() / 1024;
            }
        };
    }

    /*
    * 加载指定视频(path)缩略图
    * */
    public void loadImageThumbnail(String path, ImageView imageView){
        Bitmap bitmap = getBitmapFromLruCache(path);
        if(bitmap != null){
            //从缓存中直接读取
            imageView.setImageBitmap(bitmap);
        }else{
            LoadThumbnailAsyncTask loadTask = new LoadThumbnailAsyncTask(path,imageView);
            loadTask.execute();
        }
    }

    private Bitmap getBitmapFromLruCache(String path){
        if(mLruCache == null)
            return null;
        return mLruCache.get(path);
    }

    private void setBitmapToLruCache(String path, Bitmap bm){
        //边界处理,如果已缓存也无需再次存储
        if(getBitmapFromLruCache(path) != null || path==null || bm == null)
            return;
        mLruCache.put(path,bm);
    }

    class LoadThumbnailAsyncTask extends AsyncTask<Void,Void,Bitmap> {
        private static final int IMAGE_WIDTH = 160;
        private static final int IMAGE_HEIGHT = 90;
        private String videoPath;
        private ImageView imageView;

        public LoadThumbnailAsyncTask(String videoPath, ImageView imageView){
            this.videoPath = videoPath;
            this.imageView = imageView;
        }

        @Override
        protected Bitmap doInBackground(Void... params) {
            //获得path的视频缩略图
            Bitmap bitmap = getImageThumnail(videoPath,IMAGE_WIDTH,IMAGE_HEIGHT);
            //如果LruCache缓存不存在,则存入
            if(getBitmapFromLruCache(videoPath) == null){
                setBitmapToLruCache(videoPath,bitmap);
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //通过TAG绑定图片地址和ImageView,解决listView图片加载错位问题
            if(imageView.getTag().toString().equals(videoPath) && bitmap != null){
                imageView.setImageBitmap(bitmap);
            }
        }

        /*
        * 使用ThumbnailUtil获得缩略图
        *  图片长宽为width、height
        * */
        private Bitmap getImageThumnail(String path, int width, int height) {
            Bitmap bm = ThumbnailUtils.createVideoThumbnail(path, MediaStore.Images.Thumbnails.MICRO_KIND);
            return ThumbnailUtils.extractThumbnail(bm, width, height);
        }
    }
}
      有一点需要注意的是,这里使用了TAG将ImageView与视频路径(videopath)进行绑定,以解决列表中使用异步任务填充item图片数据错位问题。

     

      码字不易,转载请表明出处:http://blog.csdn.net/andrexpert/article/details/61419698

         

     关于DemoMaterial Design最佳体验(2):   使用RecyclerView、CardView、SwipeRefreshLayout实现下拉刷新列表


      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值