我又来划水了,这一次玩的是如何完成标题的功能,当然,代码全部贴出来没啥意义,因为百度一大堆,这里主要阐述如何实现这个功能,封装成一个可以用来多选的适配器,进行二次开发。等等,我为什么要说又...没错!要假装成很多人看的样子,这样你才会看完这段话,虽然这段话很长。
大概的原理就是通过KV存储一些数据,K用来存View的id,V存boolean(选择的状态),所有的操作基本上都基于这个集合。
//存放多选操作的KV值,类似于Map
private SparseBooleanArray multiselectList = new SparseBooleanArray();
/**
* 设置指定Item的多选状态
* @param id Item的id
* @param status 状态
*/
public void setItemMultiselectStatus(@IdRes int id, boolean status) {
multiselectList.put(id, status);
}
public void setItemMultiselectStatus(View v, boolean status) {
setItemMultiselectStatus(v.getId(), status);
}
/**
* 获取指定多选View的位置
* @param v 查找的View
* @return 返回下标。找不到返回0
*/
public int getMultiselectPosition(@NonNull View v) {
return multiselectList.indexOfKey( v.getId() ) + 1;
}
/**
* 获取指定View id的多选状态
* @param id 查询的id
* @return 状态
*/
public boolean getItemMultiselectStatus(int id) {
return multiselectList.get(id, false);
}
/**
* 获取指定View的多选状态
* @param v 查询的View
* @return 状态
*/
public boolean getItemMultiselectStatus(@NonNull View v) {
return getItemMultiselectStatus( v.getId() );
}
/**
* 获取所有Item的多选状态
* @return 状态列表
*/
public List getItemMultiselectStatusAll() {
List list = new ArrayList<>();
for (int i = 0; i < multiselectList.size(); i++) {
list.add( multiselectList.valueAt( i ) );
}
return list;
}
这样一来我们就稍微的封了一些方法用于接下来的操作,那接来下该做什么呢?那就是初始化列表中所有Item对它的一个状态。因为我们做的的是BaseAdapter,所以要尽可能的保证它被继承后不会影响原先的操作。我是这么想的,就是当执行onCreateViewHolder的时候不是要返回一个Holder吗?然后Holder需要传入一个View是吧?所以我们写一个方法用来返回这个View,那这个方法必须要有几个参数(ViewGroup、主要显示的View、多选的View),如果需要拓展的话可以增加一个枚举或者常量类决定多选View的显示位置(比如左边或者右边出现)。
/**
* 创建带多选的ItemView
* 代码有点长,最好的话独立出来可能会更好,但是我偷懒了
*
* @see #onCreateViewHolder(ViewGroup, int)
* 创建Holder的时候需要传入一个View,可以通过它进行创建。
*
* @param viewGroup {@link ViewGroup}
* @param itemView 主要展示的View
* @param multiselectView 自定义的多选View
* @param gravity 多选View显示的位置
* @return 返回创建好的View
*/
@SuppressLint("ClickableViewAccessibility")
public View createMultiselectItemView(@NonNull ViewGroup viewGroup,
@NonNull View itemView,
@NonNull View multiselectView,
@NonNull MultiselectGravity gravity) {
//多选View的Id,前边的数字可以无视掉,除非你也喜欢这么玩的话
int multiselectId = 1000000 + atoId.getAndIncrement();
int match = ViewGroup.LayoutParams.MATCH_PARENT;
//父布局,用来包裹 多选View 和 主要展示的View
LinearLayout superLayout = new LinearLayout(viewGroup.getContext());
LinearLayout.LayoutParams superLP, itemViewLP, multiselectLP;
mViewGroup = viewGroup;
superLP = new LinearLayout.LayoutParams( match, ViewGroup.LayoutParams.WRAP_CONTENT );
/* 这里可以按照需求来做,我是通过权重来决定占用的比例 */
multiselectLP = new LinearLayout.LayoutParams( 0, match );
itemViewLP = new LinearLayout.LayoutParams( 0, match );
//设置父布局参数
superLayout.setLayoutParams( superLP );
superLayout.setOrientation(LinearLayout.HORIZONTAL);
//添加子布局,这个是之前所说的拓展,根据传入的gravity决定多选View展示的位置
switch ( gravity ) {
case RIGHT:
superLayout.addView( itemView ); //itemView在左边
superLayout.addView( multiselectView ); //多选View在右边
break;
default://默认为LEFT
superLayout.addView( multiselectView ); //多选View在左边
superLayout.addView( itemView ); //itemView在右边
break;
}
/* 设置权重 */
multiselectLP.weight = 2;
itemViewLP.weight = 8;
/* 设置布局参数 */
multiselectView.setLayoutParams( multiselectLP );
itemView.setLayoutParams( itemViewLP );
//设置id,方便我们查询到它
multiselectView.setId( multiselectId );
//默认是隐藏,有需要可以设为默认显示
multiselectView.setVisibility( View.GONE );
/* 多选View的点击事件监听器,这里监听的是Touch,我不是很喜欢在Base里边设置OnClick,那样
会出现很多问题,如果通过GestureDetector那样的话可以实现很多功能 */
multiselectView.setOnTouchListener((v, event) -> {
int id = v.getId();
//这就是之前写的,用来查询当前View的位置
int position = getMultiselectPosition( v );
//取反设置当前选中的多选VIew
setItemMultiselectStatus( v, !getItemMultiselectStatus(v) );
//回调点击事件监听器
if( mOnClickMultiselectListener != null ) {
//需要说明的是 mThis 是当前适配器的 this
mOnClickMultiselectListener.onItemClick(mThis, v, position, id);
}
return false;
});
//添加多选View的id到列表中,默认隐藏
setItemMultiselectStatus( multiselectId, false );
return superLayout;
}
//多选View的点击事件监听器
private OnItemClickMultiselectListener mOnClickMultiselectListener;
/**
* 设置Item的多选操作点击事件监听器
* @param listener 监听器
*/
public void setOnItemClickMultiselectListener(OnItemClickMultiselectListener listener) {
mOnClickMultiselectListener = listener;
}
这样我们只需要传入ItemView和多选View就行了,不必再对多选View进行事件处理,只要监听就行了。做到这里我们就完成了对多选View的添加和点击操作。
//是否启用/禁用多选模式
private boolean isEnableMultiselectMode;
/**
* 设置是否启用多选模式
* @param enable 是否启用
*/
public void setEnableMultiselectMode(boolean enable) {
isEnableMultiselectMode = enable;
//通过遍历的形式隐藏和显示多选View
for (int i = 0; i < multiselectList.size(); i++) {
View v = mViewGroup.findViewById( multiselectList.keyAt( i ) );
v.setVisibility( enable ? View.VISIBLE : View.GONE );
}
}
/**
* 开启/关闭多选模式
* 为了方便,写了这个方法用来自动切换,就是单纯的取反
*/
public void switchMultiselectMode() {
setEnableMultiselectMode( !isEnableMultiselectMode );
}
/**
* 多选模式启用状态
* @return 是否启用
*/
public boolean isEnableMultiselectMode() {
return isEnableMultiselectMode;
}
好了,基本就结束了。最后我们稍微调用一下看看:
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_view, viewGroup, false);
View muView = new View(viewGroup.getContext());
muView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
muView.setBackgroundResource(R.color.colorBlack);
View v = createMultiselectItemView(viewGroup, itemView, muView, MultiselectGravity.LEFT);
return new Holder(v);
}
这是大概的调用方式,主要是展示如何使用。
接下来是点击事件这块:
//默认不启用
mAdapter.setEnableMultiselectMode( false );
//Item长按事件监听器(伪代码)
mAdapter.setOnItemLongListener((view, position) -> {
//取反,开启/关闭多选模式
mOptAdapter.switchMultiselectMode();
});
//长按多选View事件监听器
mOptAdapter.setOnItemClickMultiselectListener((adapter, v, position, id) -> {
LogUtils.e("getItemMultiselectStatus", "ID:" + mOptAdapter.getItemMultiselectStatus(v) );
LogUtils.e("getItemMultiselectStatus", "V :" + mOptAdapter.getItemMultiselectStatus(id) );
LogUtils.e("getMultiselectPosition", "" + mOptAdapter.getMultiselectPosition(v) );
for( boolean b : mOptAdapter.getItemMultiselectStatusAll() ) {
LogUtils.e("getItemMultiselectStatusAll", "id:" + id + " ==> " + b );
}
});
最后是输出日志:
/* 第一个按钮按下时 */
E/getItemMultiselectStatus: ID:true
E/getItemMultiselectStatus: V :true
E/getMultiselectPosition: 1
E/getItemMultiselectStatusAll: id:1000000 ==> true
E/getItemMultiselectStatusAll: id:1000000 ==> false
/* 第二个按钮按下时 */
E/getItemMultiselectStatus: ID:true
E/getItemMultiselectStatus: V :true
E/getMultiselectPosition: 2
E/getItemMultiselectStatusAll: id:1000001 ==> true
E/getItemMultiselectStatusAll: id:1000001 ==> true