ListView模仿——支付宝手机客户端长按ListView的Item弹出操作按钮效果

本文介绍了一种模仿支付宝客户端中ListView的实现方式,该方式通过监听长按事件并在对应的ListView项上显示操作按钮来提升用户体验。文章详细阐述了实现步骤及核心代码,并提供了完整的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果

首先上两张图:


左面一张是支付宝手机客户端的截图,右面是最终实现的效果图。长按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);
	}
}

自己的adapter需要继承这个CustomAdapter,一个简单的实现如下:
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,能力有限,不知道怎么把这个限制去掉,如果你有好的思路,欢迎指教~~

源码下载





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值