易生活(三)-APP—ninegridview源码阅读
简介
作者原话
类似QQ空间,微信朋友圈,微博主页等,展示图片的九宫格控件,自动根据图片的数量确定图片大小和控件大小,使用Adapter模式设置图片,对外提供接口回调,整合了Glide和PhotoView,点击图片全屏预览大图。
该项目是根据:https://github.com/laobie/NineGridImageView 修改而成,进行了优化扩展,使代码更加简单,喜欢原作的可以去使用。同时欢迎大家下载体验本项目,如果使用过程中遇到什么问题,欢迎反馈。
在该框架的帮助下,实现的最终效果
github地址
https://github.com/jeasonlzy0216/NineGridView
综述
- 作者通过我们传递进去的上下文和一个List对照片集合进行相关计算,对照片进行布局、质量压缩、设置监听事件等。当我们点击照片时,会把一个List集合传递到另外一个Activity中进行显示(会有相关入场出厂动画以及图片张数、当前位置等信息)。最终效果即是我们看到的效果。其实,原理很简单,但是需要扎实的基础功才能写出这样优秀的框架。
阅读模块
- 作者给的demo中的”使用RecyclerView展示news”和”使用ListView展示Evaluation”的第二个。可以直接下载demo阅读。
源码阅读
程序入口
holder.nineGrid.setAdapter(new ClickNineGridViewAdapter(context, imageInfo));
该层职责:准备需要的参数;
需要设置一个适配器,适配器里面是一个写好的适配器参数,需要传入两个参数,一个上下文、一个List,程序通过new ClickNineGridViewAdapter(context, imageInfo)
返回一个父类NineGridViewAdapter
NineGridView
作者重写ViewGroup,这一块作者做了大量工作进行图片数量的判断、位置的处理、图片间距的设置、图片质量的设置等,下面进行图片宽高的处理,具体也不是看的太懂,还有其它地方的细节处理。(如何获取数据:通过传入的NineGridViewAdapter对象,持有上下文和list对象。)
该适配器的职责:
根据参数设置排版好图片布局;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = 0;
int totalWidth = width - getPaddingLeft() - getPaddingRight();
if (mImageInfo != null && mImageInfo.size() > 0) {
if (mImageInfo.size() == 1) {
gridWidth = singleImageSize > totalWidth ? totalWidth : singleImageSize;
gridHeight = (int) (gridWidth / singleImageRatio);
//矫正图片显示区域大小,不允许超过最大显示范围
if (gridHeight > singleImageSize) {
float ratio = singleImageSize * 1.0f / gridHeight;
gridWidth = (int) (gridWidth * ratio);
gridHeight = singleImageSize;
}
} else {
// gridWidth = gridHeight = (totalWidth - gridSpacing * (columnCount - 1)) / columnCount;
//这里无论是几张图片,宽高都按总宽度的 1/3
gridWidth = gridHeight = (totalWidth - gridSpacing * 2) / 3;
}
width = gridWidth * columnCount + gridSpacing * (columnCount - 1) + getPaddingLeft() + getPaddingRight();
height = gridHeight * rowCount + gridSpacing * (rowCount - 1) + getPaddingTop() + getPaddingBottom();
}
setMeasuredDimension(width, height);
}通过获取到的NineGridViewAdapter对象遍历图片然后分别回调ClickNineGridViewAdapter中的图片单击事件以及相关。
代码片段1
/** 设置适配器 */
public void setAdapter(@NonNull NineGridViewAdapter adapter) {
//省略代码...
//保证View的复用,避免重复创建
if (mImageInfo == null) {
for (int i = 0; i < imageCount; i++) {
ImageView iv = getImageView(i);//这个地方调用
if (iv == null) return;
addView(iv, generateDefaultLayoutParams());
}
}
//省略代码...
}
代码片段2
/** 获得 ImageView 保证了 ImageView 的重用 */
private ImageView getImageView(final int position) {
ImageView imageView;
if (position < imageViews.size()) {
imageView = imageViews.get(position);
} else {
imageView = mAdapter.generateImageView(getContext());//回调NineGridViewAdapter中的方法进行图片简单处理
//回调点击事件
imageView.setOnClickListener(new OnClickListener() {
//省略代码...
}
适配器
new ClickNineGridViewAdapter(context, imageInfo)
该适配器的职责:
1. 负责图片的预览功能(包括入场动画、出厂动画、图片下标等),该功能通过NineGridView回调调用。
代码片段1,图片点击事件
@Override
protected void onImageItemClick(Context context, NineGridView nineGridView, int index, List<ImageInfo> imageInfo) {
//省略代码...
}
代码片段2 ,跳转代码片段
@Override
protected void onImageItemClick(Context context, NineGridView nineGridView, int index, List<ImageInfo> imageInfo) {
//省略代码...
Intent intent = new Intent(context, ImagePreviewActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable(ImagePreviewActivity.IMAGE_INFO, (Serializable) imageInfo);
bundle.putInt(ImagePreviewActivity.CURRENT_ITEM, index);
intent.putExtras(bundle);
context.startActivity(intent);//跳转
((Activity) context).overridePendingTransition(0, 0);
}
图片预览ImagePreviewActivity
该部分主要处理了图片的入场动画、图片显示大小、下面的文字提醒、图片切换等效果,核心使用viewpager实现,下面是滑动事件的处理
@Override protected void onCreate(Bundle savedInstanceState) { //省略代码... viewPager.setCurrentItem(currentItem); viewPager.getViewTreeObserver().addOnPreDrawListener(this); viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { currentItem = position;//实时更新当前位置 tv_pager.setText(String.format(getString(R.string.select), currentItem + 1, imageInfo.size())); } }); tv_pager.setText(String.format(getString(R.string.select), currentItem + 1, imageInfo.size()));//更改下面的提醒文字,当前第几张 }
其它学习到的
setContentView()和addContentView() 的区别
两者的区别主要包括两点:
以添加UI组件是否被移除
setContentView() 会导致先前添加的被移除, 即替换性的;
而 addContentView() 不会移除先前添加的UI组件,即是累积性的是否控制布局参数
addContentView() 有两个参数, 可以控制布局参数; 你指出的这个setContentView 没有接受布局参数,
默认使用MATCH_PARENT; 不过setContentView()也有带两个参数的版本, 可以控制布局参数。
在加载适配器之前会有一个空挡在加载数据,页面时空白的,用户体验不好。可以使用以下方法加载一个“正在加载..”页面。
iew emptyView = View.inflate(this, R.layout.item_empty, null); addContentView(emptyView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); listView.setEmptyView(emptyView);