前言:今天遇到个需求,需要让用户动态选择语音传输方式的次序,突然想起支付宝选择扣款顺序的功能,恰好能满足需要,就花了点时间写了个demo,在此权当学习记录
思路:
在BaseAdapter的getView方法中,我们要设置三个点击事件
当前view的点击事件,即ListView的item的点击事件,点击时将该item的向上和向下的图标按钮设为可见
向上图标按钮的点击事件,点击时将该item往上移
向下图标按钮的点击时间,点击时将该item往下移
item向上向下移动的原理
实现:
首先定义ListView的item布局item_change.xml,
很简单,在线性布局中放置三个控件,TextView用于显示文字内容,两个ImageButton分别放置向上和向下的图标按钮,并在初始的时候将这两个按钮设为不可见。
接下来看Adapter的代码,定义ChangeAdapter继承自BaseAdapter
public class ChangeAdapter extends BaseAdapter implements View.OnClickListener {
private ArrayList itemList;
private Context mContext;
private Callback mCallback;
private int mCurPosition;//定义该变量来标记当前item的点击位置
//定义回调接口实现ListView内Item的内部控件的点击事件
public interface Callback {
public void click(View v);
}
public ChangeAdapter(Context mContext, ArrayList itemList, Callback mCallback, int mCurPosition) {
this.mContext = mContext;
this.itemList = itemList;
this.mCallback = mCallback;
this.mCurPosition = mCurPosition;
}
@Override
public int getCount() {
return itemList.size();
}
@Override
public Object getItem(int position) {
return itemList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_change, null);
viewHolder = new ViewHolder();
viewHolder.itemName = (TextView) convertView.findViewById(R.id.item_name);
viewHolder.upBtn = (ImageButton) convertView.findViewById(R.id.up);
viewHolder.downBtn = (ImageButton) convertView.findViewById(R.id.down);
convertView.setTag(R.id.tag_viewholder, viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag(R.id.tag_viewholder);
}
viewHolder.itemName.setText(itemList.get(position));
//根据点击或者向上向下操作的item的当前位置,来控制向上和向下的按钮的可见与否
if (mCurPosition == position && mCurPosition == 0) {
viewHolder.downBtn.setVisibility(View.VISIBLE);
} else if (mCurPosition == position && mCurPosition == itemList.size() - 1) {
viewHolder.upBtn.setVisibility(View.VISIBLE);
} else if (mCurPosition == position && mCurPosition != 0 && mCurPosition != itemList.size() - 1) {
viewHolder.upBtn.setVisibility(View.VISIBLE);
viewHolder.downBtn.setVisibility(View.VISIBLE);
} else {
viewHolder.upBtn.setVisibility(View.INVISIBLE);
viewHolder.downBtn.setVisibility(View.INVISIBLE);
}
//设置item向下移动的点击时间并标志其位置
viewHolder.downBtn.setOnClickListener(this);
viewHolder.downBtn.setTag(position);
//设置item向上移动的点击时间并标志其位置
viewHolder.upBtn.setOnClickListener(this);
viewHolder.upBtn.setTag(position);
//设置整个item的点击时间并标志其位置
convertView.setOnClickListener(this);
convertView.setTag(R.id.tag_item_click, position);
return convertView;
}
class ViewHolder {
TextView itemName;
ImageButton upBtn;
ImageButton downBtn;
}
//定义item内部控件的点击事件由回调接口定义的点击方法来处理
@Override
public void onClick(View v) {
mCallback.click(v);
}
//在对数据进行处理后,调用该方法,通知adapter刷新数据
public void refresh(int currentPosition) {
mCurPosition = currentPosition;
notifyDataSetChanged();
}
}
Adapter这部分代码不难,几个注意的点稍微讲解下
ListView的Item的内部控件,即向上和向下的图标按钮的点击事件的实现.
我们定义了如下的回调接口,并在该接口中定义了一个click方法
//定义回调接口实现ListView内Item的内部控件的点击事件
public interface Callback {
public void click(View v);
}
我们在ChangeAdapter实现了OnClickListener接口,并在getView方法中,对整个Item,及Item的内部的两个ImageViewButton定义了点击事件
//设置item向下移动的点击时间并标志其位置
viewHolder.downBtn.setOnClickListener(this);
viewHolder.downBtn.setTag(position);
//设置item向上移动的点击时间并标志其位置
viewHolder.upBtn.setOnClickListener(this);
viewHolder.upBtn.setTag(position);
//设置整个item的点击时间并标志其位置
convertView.setOnClickListener(this);
convertView.setTag(R.id.tag_item_click, position);
接下来看onClick方法,可以看到我们item和图标按钮的点击这里交给了前面定义的回调Callback中的click方法来处理
@Override
public void onClick(View v) {
mCallback.click(v);
}
所以这样就明显了,我们在要调用ChangeAdapter的Activity里实现这里定义的Callback接口,并将其作为ChageAdapter构造方法的一部分,由于item的内部控件点击事件会由Callback处理,而此时Activity又实现了Callback,相应的点击事件就可以由Activity处理。
public ChangeAdapter(Context mContext, ArrayList itemList, Callback mCallback, int mCurPosition) {
this.mContext = mContext;
this.itemList = itemList;
this.mCallback = mCallback;
this.mCurPosition = mCurPosition;
}
这部分就是我们在点击item时,根据该item的位置,刷新adapter。点击位置为0时只显示向下按钮,点击位置为最后一个时只显示向上按钮,点击其他位置时,向上和向下按钮都设为可见
//根据点击或者向上向下操作的item的当前位置,来控制向上和向下的按钮的可见与否
if (mCurPosition == position && mCurPosition == 0) {
viewHolder.downBtn.setVisibility(View.VISIBLE);
} else if (mCurPosition == position && mCurPosition == itemList.size() - 1) {
viewHolder.upBtn.setVisibility(View.VISIBLE);
} else if (mCurPosition == position && mCurPosition != 0 && mCurPosition != itemList.size() - 1) {
viewHolder.upBtn.setVisibility(View.VISIBLE);
viewHolder.downBtn.setVisibility(View.VISIBLE);
} else {
viewHolder.upBtn.setVisibility(View.INVISIBLE);
viewHolder.downBtn.setVisibility(View.INVISIBLE);
}
向上和向下按钮的setTag方法都只需传入position,以后就可以在Activit中,用getTag取到当前点击item的position。
而item的setTag就比较特殊
//设置整个item的点击时间并标志其位置
convertView.setOnClickListener(this);
convertView.setTag(R.id.tag_item_click, position);
追一下代码就能知道,在getView方法中,有两个地方用到了convertView.setTag方法,第一个要存入的是一个ViewHolder对象,第二个存入的是当前item的position。所以需要用不同的key来标志。不然你在调用getTag时必然会出错。
convertView.setTag(R.id.tag_viewholder, viewHolder);
而这里的key需要是resourceId,所以我们在values/strings.xml,定义两个item来作为key
这样以后就可以通过view.getTag(key),取到相应的值
刷新adapter,我们定义了一个refresh方法,在点击的位置发生改变后,通知adapter刷新
//在对数据进行处理后,调用该方法,通知adapter刷新数据
public void refresh(int currentPosition) {
mCurPosition = currentPosition;
notifyDataSetChanged();
}
定义布局activity_main.xml,就放一个ListView
MainActivity:
public class MainActivity extends AppCompatActivity implements ChangeAdapter.Callback {
private ListView lv;
private ChangeAdapter adapter;
private ArrayList itemList;
private int currentPosition = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initAndSetView();
}
private void initAndSetView() {
lv = (ListView) findViewById(R.id.lv_change);
initData();
adapter = new ChangeAdapter(this, itemList, this, currentPosition);
lv.setAdapter(adapter);
}
@Override
public void click(View v) {
int curPosition;
int mCurPosition;
switch (v.getId()) {
//整个item点击事件的处理逻辑
case R.id.item:
mCurPosition = (int) v.getTag(R.id.tag_item_click);
currentPosition = mCurPosition;
adapter.refresh(currentPosition);
break;
//向上图标按键点击事件的处理逻辑
case R.id.up:
curPosition = (int) v.getTag();
if (curPosition != 0) {
String upFirst = itemList.get(curPosition);
String upSecond = itemList.get(curPosition - 1);
itemList.remove(curPosition);
itemList.remove(curPosition - 1);
itemList.add(curPosition - 1, upFirst);
itemList.add(curPosition, upSecond);
currentPosition = curPosition - 1;
adapter.refresh(currentPosition);
}
break;
//向下图标按键点击事件的处理逻辑
case R.id.down:
curPosition = (int) v.getTag();
if (curPosition != itemList.size() - 1) {
String downFirst = itemList.get(curPosition);
String downSecond = itemList.get(curPosition + 1);
itemList.remove(curPosition + 1);
itemList.remove(curPosition);
itemList.add(curPosition, downSecond);
itemList.add(curPosition + 1, downFirst);
currentPosition = curPosition + 1;
adapter.refresh(currentPosition);
}
break;
default:
break;
}
}
//初始化填充数据
private void initData() {
itemList = new ArrayList();
itemList.add("余额宝");
itemList.add("蚂蚁花呗");
itemList.add("余额");
itemList.add("工商银行储蓄卡(1689)");
itemList.add("花呗分期");
}
}
这部分逻辑很简单,如之前所讲的,MainActivity实现ChangeAdapter的Callback接口,并实现接口的click方法,注意导包不要导错了。而后在adapter的构造方法中,传入this,这样item及其内部控件的点击事件就可以在MainActivity中处理。重点看click方法:
@Override
public void click(View v) {
int curPosition;
int mCurPosition;
switch (v.getId()) {
//整个item点击事件的处理逻辑
case R.id.item:
mCurPosition = (int) v.getTag(R.id.tag_item_click);
currentPosition = mCurPosition;
adapter.refresh(currentPosition);
break;
//向上图标按键点击事件的处理逻辑
case R.id.up:
curPosition = (int) v.getTag();
if (curPosition != 0) {
String upFirst = itemList.get(curPosition);
String upSecond = itemList.get(curPosition - 1);
itemList.remove(curPosition);
itemList.remove(curPosition - 1);
itemList.add(curPosition - 1, upFirst);
itemList.add(curPosition, upSecond);
currentPosition = curPosition - 1;
adapter.refresh(currentPosition);
}
break;
//向上图标按键点击事件的处理逻辑
case R.id.down:
curPosition = (int) v.getTag();
if (curPosition != itemList.size() - 1) {
String downFirst = itemList.get(curPosition);
String downSecond = itemList.get(curPosition + 1);
itemList.remove(curPosition + 1);
itemList.remove(curPosition);
itemList.add(curPosition, downSecond);
itemList.add(curPosition + 1, downFirst);
currentPosition = curPosition + 1;
adapter.refresh(currentPosition);
}
break;
default:
break;
}
}
对于item的点击事件,我们用之前讲的getTag(key)方法并用强制类型转换获取该item的position,并调用之前定义的refresh方法,通知adapter当前的position数据更新了,而后adapter中就会根据这个position处理向上和向下的图标按钮的显示与否。
对于向上的ImageButton的点击事件,我们用getTag方法获取其position,并调换当前位置与前一个位置的数据的值,就实现了item的向上移动的效果。注意对于第一个item,我们不处理向上移动的逻辑,所以这里要加个判断。处理完之后,调用refresh方法,就可以刷新数据。
对于向下的ImageButton的点击事件,与向上的ImageButton的逻辑是相似的,这里就不再赘述。
要注意的是ArrayList的remove方法,当你remove了一个position的数据,后面位置的数据就会前移,所以这里处理数据要小心点,稍微思考下就能明白。
在这里,item位置移动后退出并没有存储,可以用SharedPreference进行存储,在下次打开后能重现之前的更改。
以上就是全部的实现,要是有什么讲错或者有改进的地方,欢迎指出