效果
首先上两张图:
左面一张是支付宝手机客户端的截图,右面是最终实现的效果图。长按listView的item,会弹出一个View覆盖在长按的那个item上。点击相应按钮,即可执行对应的操作。点击手机返回键隐藏操作按钮组。当滚动ListView时,显示的按钮组离开当前界面后会消失。
分析
之前很多时候,自己的想法就是长按item,弹出一个dialog,在dialog中做一些操作然后反映到listview上。相信很多人也跟我一样。看到这个效果之后感觉在用户体验上比dialog有不少进步,遂想记录之。当然还有一些更酷更炫的效果,比如QQ的在item上滑动显示一个删除按钮,其他的左滑和右滑实现功能,拖动改变item顺序,这些在之后的文章中继续模仿
分析下,这个功能模仿到形似很简单:监听用户长按,记录当前item的ID,然后通知adapter数据改变,重新计算item的时候显示操作按钮组;用户点击返回时,若当前有操作按钮组显示则首先隐藏,这个只需要将item的ID置位即可;当用户将当前item滑出屏幕再重新计算item时将按钮组隐藏,这个在listview的OnScrollListener中处理。复杂一点的没有item不同的时候显示不通的操作按钮组,这个暂时没有实现。
实现
这里直接贴核心代码了,代码中都有注释:
/**
* 自定义的ListView,模仿支付宝手机客户端的第一个界面的ListView效果:长按某个Item时在Item上显示操作按钮<br>
* 个人认为, 这个体验应该比长按弹出对话框要好<br>
* 实现思路:<br>
* 监听用户长按,记录当前item的ID,然后显示操作按钮<br>
* 调用方法:<br>
* 自定义的ListViewAapter继承这个CustomAdapter实现必要的方法即可
*
* @author ttdevs
*
*/
public abstract class CustomAdapter extends BaseAdapter implements OnItemLongClickListener, OnScrollListener {
private Context mContext;
private ListView mListView;
private List<OperateList> mOpList;
private LinearLayout mOperateLayout;
private OnCustomItemLongClickListener mListener;
private int showPosition = -1;
public CustomAdapter(Context context, ListView listView) {
mContext = context;
mListView = listView;
mListView.setOnItemLongClickListener(this);
mListView.setOnScrollListener(this);
}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (mOpList != null) {
showPosition = position;
notifyDataSetChanged();
}
return true;
}
public void setOnCustomItemLongClick(List<OperateList> opList, OnCustomItemLongClickListener listener) {
if (opList != null && opList.size() > 0 && listener != null) {
mOpList = opList;
mListener = listener;
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 这个地方主要处理选中item被滑出界面时隐藏按钮组的逻辑
if (showPosition > (firstVisibleItem + visibleItemCount) || showPosition < firstVisibleItem) {
showPosition = -1;
}
}
/**
* 隐藏操作按钮组:置位showPostion,通知adapter数据有变
*
* @return
*/
public boolean hideOperateView() {
if (showPosition == -1) {
return false;
} else {
showPosition = -1;
notifyDataSetChanged();
return true;
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
System.err.println(position + "convertView == null>>>>>" + (convertView == null));
convertView = getCustomView(position, convertView, parent);
if (position == showPosition) {
addOperateButtonView(position, convertView, parent);
}
return convertView;
}
/**
* 自定义的一个方法,用来替代getView(),这样可以保证执行完创建Item之后做自己想要的事情:添加操作按钮组到item上
*
* @param position
* @param convertView
* @param parent
* @return
*/
public abstract View getCustomView(int position, View convertView, ViewGroup parent);
/**
* 添加操作按钮所在的布局,这个地方要求ListView的Item必须为FrameLayout,否则会报错
*
* @param position
* @param convertView
* @param parent
*/
private void addOperateButtonView(int position, View convertView, ViewGroup parent) {
if (mOpList == null) {
return;
}
try {
FrameLayout flOperate = (FrameLayout) convertView;
// if (mOperateLayout == null) { // TODO 这个地方加上会没有效果,很奇怪
mOperateLayout = new LinearLayout(mContext);
mOperateLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mOperateLayout.setOrientation(LinearLayout.HORIZONTAL);
mOperateLayout.setBackgroundColor(Color.parseColor("#88FE9D3D"));
mOperateLayout.setGravity(Gravity.CENTER);
// }
for (int i = 0; i < mOpList.size(); i++) {
addOperateButton(mOperateLayout, mOpList.get(i), position, convertView);
if (i != mOpList.size() - 1) {
addOperateButtonDivider(mOperateLayout);
}
}
flOperate.addView(mOperateLayout);
} catch (Exception e) {
String toast = "CustomAdapter:ListView's item layout must be FrameLayout!";
Toast.makeText(mContext, toast, Toast.LENGTH_LONG).show();
}
}
/**
* 添加一个按钮,先创建一个线性布局,然后在里面添加ImageView和TextView
*
* @param mOperateLayout
* @param opList
* @param position
* @param convertView
*/
@SuppressWarnings("deprecation")
public void addOperateButton(LinearLayout mOperateLayout, OperateList opList, final int position,
final View convertView) {
final int flag = opList.getFlag();
LinearLayout llButtom = new LinearLayout(mContext);
llButtom.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
llButtom.setOrientation(LinearLayout.HORIZONTAL);
llButtom.setGravity(Gravity.CENTER);
// llButtom.setBackground(getSelectorDrawable());
llButtom.setBackgroundDrawable(getSelectorDrawable());
llButtom.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.onCustomItemLongClick(flag, position, convertView);
}
});
ImageView ivIcon = new ImageView(mContext);
ivIcon.setImageResource(opList.getDrawableId());
ivIcon.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
llButtom.addView(ivIcon);
TextView tvOperate = new TextView(mContext);
tvOperate.setText(opList.getContent());
tvOperate.setGravity(Gravity.CENTER_VERTICAL);
tvOperate.setTextSize(16);
tvOperate.setTextColor(Color.parseColor("#FF3399FF"));
llButtom.addView(tvOperate);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
lp.weight = 1;
mOperateLayout.addView(llButtom, lp);
}
/** 添加一条分隔线 */
public void addOperateButtonDivider(LinearLayout mOperateLayout) {
ImageView ivBg = new ImageView(mContext);
ivBg.setBackgroundColor(Color.parseColor("#FFFFFFFF"));
LinearLayout.LayoutParams lpDivider = new LinearLayout.LayoutParams(2, LayoutParams.MATCH_PARENT);
lpDivider.setMargins(0, 6, 0, 6);
mOperateLayout.addView(ivBg, lpDivider);
}
/** 获取一个selector */
public Drawable getSelectorDrawable() {
ColorDrawable cdNormal = new ColorDrawable(Color.parseColor("#88FE9D3D"));
ColorDrawable cdPressed = new ColorDrawable(Color.parseColor("#EEFE9D3D"));
StateListDrawable sld = new StateListDrawable();
sld.addState(new int[] { -android.R.attr.state_pressed }, cdNormal);
sld.addState(new int[] { android.R.attr.state_pressed }, cdPressed);
return sld;
}
/** 自定义的接口,处理用户单击返回 */
public interface OnCustomItemLongClickListener {
public void onCustomItemLongClick(int flag, int position, View itemView);
}
}
public class ListViewAdapter extends CustomAdapter {
private String[] mValues;
private LayoutInflater mInflater;
private Context mContext;
public ListViewAdapter(Context context, ListView listView, String[] values) {
super(context, listView);
mContext = context;
mInflater = LayoutInflater.from(context);
mValues = values;
}
@Override
public int getCount() {
return mValues.length;
}
@Override
public Object getItem(int position) {
return mValues[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getCustomView(int position, View convertView, ViewGroup parent) {
// 此处不是重点,简单写之
convertView = mInflater.inflate(R.layout.item_main, null);
TextView tvTitle = (TextView)convertView.findViewById(R.id.tvTitle);
tvTitle.setText(mValues[position]);
return convertView;
}
}
public class MainActivity extends Activity implements OnCustomItemLongClickListener {
private String[] mStrings = Cheeses.sCheeseStrings;
private ListView lvMain;
private List<OperateList> opList;
private ListViewAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
lvMain = (ListView) findViewById(R.id.lvMain);
adapter = new ListViewAdapter(this, lvMain, mStrings);
adapter.setOnCustomItemLongClick(opList, this);
lvMain.setAdapter(adapter);
}
@Override
public void onBackPressed() {
if (!adapter.hideOperateView()) {
super.onBackPressed();
}
}
private void initData() {
opList = new ArrayList<OperateList>();
OperateList ob = new OperateList();
ob.setDrawableId(R.drawable.ic_undo);
ob.setContent("撤销");
ob.setFlag(1);
opList.add(ob);
}
@Override
public void onCustomItemLongClick(int flag, int position, View itemView) {
System.out.println("onCustomItemLongClick:" + flag + "||" + position + "||" + itemView);
}
}
这样就实现最后的效果。
总结
设计的时候本想把他设计的更通用些,这样以后用着也方便,尝试了下去自定义listview,同时自定义listview和adapter,但是都不满意,最终决定还是只自定义一个adapter。仔细的你可能已经发现,还有很多局限性,比如item的布局必须是FrameLayout,能力有限,不知道怎么把这个限制去掉,如果你有好的思路,欢迎指教~~