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:[email protected]
* Blog:http://blog.csdn.net/andrexpert
* MyApp:http://www.anzhi.com/soft_2729689.html (安智市场)
*/
public class VideoInfoListAdapter extends RecyclerView.Adapter {
private Context mContext;
private VideoThumbnailUtil mThumbnailUtil;
private List 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 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
接下来,你或许注意到了,我们这个Demo不仅使用RecyclerView、CardView来展示列表数据,还具有下拉刷新列表来获取最新列表数据的功能,而实现这个功能控件就是SwipeRefreshLayout。SwipeRefreshLayout由support-v4提供具有Material特性的下拉刷新的核心类,它实现的过程相当简单,即我们只需要将想要实现下拉刷新的控件放置到SwipeRefreshLayout中,就可以迅速让这个控件支持下拉刷新。activity_main.xml完整代码:
从activity_main.xml代码可知,我们只需要在RecyclerView包裹一层SwipeRefreshLayout,那么RecyclerView就自动拥有了下拉刷新功能。需要注意的是,由于我们的AppBarLayout需要响应RecyclerView的滚动事件,还需要将app:layout_behavior属性移动至SwipeRefreshLayout。既然RecyclerView拥有了下拉刷新的功能,自然下一步就是如何去响应处理下拉刷新事件了,MainActivity.claa完整代码如下:
/**
* 视频列表
* Created by jiangdongguo on2017/3/8.
* e-mail:[email protected]
* 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 getListAdapterData() {
ListmInfoBeans = 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:[email protected]
* Blog:http://blog.csdn.net/andrexpert
* MyApp:http://www.anzhi.com/soft_2729689.html (安智市场)
*/
public class VideoThumbnailUtil {
private LruCache mLruCache;
public VideoThumbnailUtil(){
//设备内存大小
int maxMemory = (int) Runtime.getRuntime().maxMemory();
//缓存大小
int cacheSize = maxMemory / 8;
//实例化LruCache,重写sizeof衡量每张图片的大小,返回图片的数量
mLruCache = new LruCache(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 {
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
关于Demo:Material Design最佳体验(2): 使用RecyclerView、CardView、SwipeRefreshLayout实现下拉刷新列表