仿微信朋友圈9图上传选择器
本人最新公众号<Android百科全书>,汇集了各个公众号的优秀文章,进行分类整理,让大家能够更方便的查阅,希望大家多多支持,来个关注奥,一个号,顶一堆号。
这个开源项目,之前就想写,一直没有时间整理,这次整理出来,方便以后使用,封装成了库,支持定制,废话不多说,先上图
这是微信的朋友圈发布选择器,一般大家都是用recyclerview或者gridview写一个出来,然后里面再做其他处理,当时我就想,我能不能把它封装成一个控件,然后以后就再也不用写了,得出的结论是能,于是开始封装,封装完成呢,效果图就是下面这个
动图太大上传不了了,那就放出github地址来
单纯的上传图片展示控件ImageShowPicker
这里的readme有动态图,大家可以看下。
废话不多说,大家先来看看怎么使用这个控件
ImageShowPickerView pickerView = (ImageShowPickerView)findViewById(R.id.it_picker_view);
final List<ImageBean> list = getItem(position);
pickerView.setImageLoaderInterface(new Loader());
pickerView.setNewData(list);
//展示有动画和无动画
//设置监听
pickerView.setPickerListener(new ImageShowPickerListener() {
@Override
public void addOnClickListener(int remainNum) {
Toast.makeText(context, "remainNum" + remainNum, Toast.LENGTH_SHORT).show();
//在listview或recyclerview才会使用这个list.add(),其他情况都不用
list.add(new ImageBean("http://pic78.huitu.com/res/20160604/1029007_20160604114552332126_1.jpg"));
pickerView.addData(new ImageBean("http://pic78.huitu.com/res/20160604/1029007_20160604114552332126_1.jpg"));
}
@Override
public void picOnClickListener(List<ImageShowPickerBean> list, int position, int remainNum) {
Toast.makeText(context, list.size() + "========" + position + "remainNum" + remainNum, Toast.LENGTH_SHORT).show();
}
@Override
public void delOnClickListener(int position, int remainNum) {
list.remove(position);
Toast.makeText(context, "delOnClickListenerremainNum" + remainNum, Toast.LENGTH_SHORT).show();
}
});
//所有设置完毕后调用该方法
pickerView.show();
//获取所有数据
pickerView.getDataList();
声明控件后做一些设置就使用这个控件了,是不是比之前的自己写recyclerview简单多了,再也不用一次次重写了, 这里就不做过多说明,更详细的使用方法,请大家移步github,欢迎star呀
下面,来为大家做一下介绍,看看我们的这个picker到底是怎么封装的,我是通过这几部来封装的
- 1、分析需求,分析定制目标
- 2、选择实现方法
- 3、编写代码
- 4、完善方法 以上4步就是我这个项目的经历过程,下面一一介绍,并说明原理
1、分析需求,分析定制目标
我们要写的这个选择器,首先他要轻便,就是不抢其他人的工作,不去给开发者带来其他的局限性,so我们就不能
直接指定某个图片加载框架,者就是一个需求;其次,我们的选择器应该有多种定制选择,比如微信的选择器就没
有删除小按钮,但是其他的app里有,我们就要定制这一个属性,让用户来选择是否显示,还有recyclerview自
带的增加删除动画,有的app就想要,有的就不想要,so我们也要提供方法让使用者来定制。
2、选择实现方法
实现方法的选择是个问题,关于图片加载的实现方法,我参考了[banner]这个开源项目的接口处理,十分感谢该
项目给我的思路,他的地址如下https://github.com/youth5201314/banner,解决了这个问题,还有一个
问题困扰我,就是如何获取数据,开始我想用反射来进行操作,但是反射的性能不好,同时使用者混淆代码时,又
增添了很多麻烦,因为数据类的参数的不确定性,所以抛弃了这种处理方式,最后选择继承重写方法的这个思路来
实现。
3、编写代码
解决了上面两个问题,下面就可以开始写代码了,一点点来说
(1).为view 设置属性
这里一定要注意,自定义控件设置属性后一定要调用typedArray.recycle();这个方法,否则后果你可以试试,爽到飞起
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImageShowPickerView);
mPicSize = typedArray.getDimensionPixelSize(R.styleable.ImageShowPickerView_pic_size, SizeUtils.getSizeUtils().dp2px(getContext(), PIC_SIZE));
isShowDel = typedArray.getBoolean(R.styleable.ImageShowPickerView_is_show_del, true);
isShowAnim = typedArray.getBoolean(R.styleable.ImageShowPickerView_is_show_anim, false);
mAddLabel = typedArray.getResourceId(R.styleable.ImageShowPickerView_add_label, R.mipmap.image_show_piceker_add);
mDelLabel = typedArray.getResourceId(R.styleable.ImageShowPickerView_del_label, R.mipmap.image_show_piceker_del);
oneLineShowNum = typedArray.getInt(R.styleable.ImageShowPickerView_one_line_show_num, ONE_LINE_SHOW_NUM);
maxNum = typedArray.getInt(R.styleable.ImageShowPickerView_max_num, MAX_NUM);
typedArray.recycle();
(2).添加数据时刷新,这里就包括动画的处理了
这只是其中一个方法,写到这里就是为了让大家了解一下,提供个思路
/**
* 添加新数据
*
* @param bean
* @param <T>
*/
public <T extends ImageShowPickerBean> void addData(T bean) {
if (bean == null) {
return;
}
this.list.add(bean);
if (isShowAnim) {
if (adapter != null) {
adapter.notifyItemChanged(list.size() - 1);
adapter.notifyItemChanged(list.size());
}
} else {
adapter.notifyDataSetChanged();
}
}
(3).重头戏来了,也就是加载图片接口
我们定义这个接口,用来实现加载图片的自定义化,无论你用什么框架,就都可以兼容了,第一眼看,有的人可能没明白怎么处理,其实就是继承接口后重写这个方法,我们在view里只不过把这个方法当成已经写好的方法,调用了一下他的方法名,这样解释是不是就更清晰些,动手试试就懂了
/**
* Author 姚智胜
* Version V1.0版本
* Description: 加载图片接口
* Date: 2017/4/6
*/
public interface ImageLoaderInterface<T extends View> extends Serializable {
void displayImage(Context context, String path, T imageView);
void displayImage(Context context, @DrawableRes Integer resId, T imageView);
T createImageView(Context context);
}
(4).RecyclerView的holder处理
把这个放到这里,主要是为了给大家看下onclick事件应该写在哪里,如果写到onBindViewHolder这个方法里,那就是无限的重复创建了,没有理解Holder的作用,我们要在最开始就对他进行创建,然后去复用他
//自定义的ViewHolder,持有每个Item的的所有界面元素
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public View iv_pic;
public ImageView iv_del;
private ImageShowPickerPicListener picOnClickListener;
public ViewHolder(View view, ImageLoaderInterface imageLoaderInterface, ImageShowPickerPicListener picOnClickListener) {
super(view);
this.picOnClickListener = picOnClickListener;
iv_pic = imageLoaderInterface.createImageView(view.getContext());
FrameLayout.LayoutParams pic_params = new FrameLayout.LayoutParams(iconHeight,
iconHeight);
pic_params.setMargins(10, 10, 10, 10);
iv_pic.setLayoutParams(pic_params);
iv_del = new ImageView(view.getContext());
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.TOP | Gravity.END;
iv_del.setPadding(5, 5, 5, 5);
iv_del.setLayoutParams(layoutParams);
iv_pic.setId(R.id.iv_image_show_picker_pic);
iv_del.setId(R.id.iv_image_show_picker_del);
iv_pic.setOnClickListener(this);
iv_del.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int i = v.getId();
if (i == R.id.iv_image_show_picker_pic) {
picOnClickListener.onPicClickListener(getLayoutPosition());
} else if (i == R.id.iv_image_show_picker_del) {
picOnClickListener.onDelClickListener(getLayoutPosition());
}
}
}
(5).意外发现recyclerview的bug
在代码里,我们在adapter内部使用了notifyItemChanged,程序却异常崩溃了,查阅过资料后,发现这个google的bug,所以重写了这个布局管理器,处理这个问题,算是意外收获
/**
* Author 姚智胜
* Version V1.0版本
* Description: 处理recyclerview在adapter内调用notifyItemChanged崩溃的解决方法
* Date: 2017/4/15
*/
public class MyGridLayoutManager extends GridLayoutManager {
public MyGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public MyGridLayoutManager(Context context, int spanCount) {
super(context, spanCount);
}
public MyGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
}
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//override this method and implement code as below
try {
super.onLayoutChildren(recycler, state);
} catch (Exception e) {
e.printStackTrace();
}
}
}
(6).数据类的父类
原理很简单,只是思路问题,我们继承这个父类后,只需重写两个方法,把我们数据类的url,为指定方法赋值就可以了,一个小思路,有可能不是最好的,希望有更好意见的来提下意见
/**
* Author 姚智胜
* Version V1.0版本
* Description: 显示数据类的父类,必须继承于该类
* Date: 2017/4/10
*/
public abstract class ImageShowPickerBean {
public String getImageShowPickerUrl() {
return setImageShowPickerUrl();
}
public int getImageShowPickerDelRes() {
return setImageShowPickerDelRes();
}
/**
* 为URL赋值,必须重写方法
*
* @return
*/
public abstract String setImageShowPickerUrl();
/**
* 为删除label赋值,必须重写方法
*
* @return
*/
public abstract int setImageShowPickerDelRes();
}
4、完善方法
这里要做的其实就是小修小补,比如我们的封装的view ,在最后的时候我又增加了几个自定义属性,让他的可定制性更强,也是在这个步骤里对我们的封装进行最后一次的检查和完善。