第一次写博客,先说下大概思路吧~
要显示item侧滑显示删除,置顶。首先要隐藏一部分item的布局(自定义隐藏布局宽度,在adapter里设置LayoutParams)。然后重写listview的onInterceptTouchEvent()和onTouchEvent()方法,然后对listview的滑动进行判断,最后进行相应的操作(删除啦,置顶啦,取消置顶bulabula)。删除需要dataList.remove(position),置顶就是将点击的item先执行dataList.add(0,object),然后执行dataList.remove(position),最后adapter.notifyDataSetChanged();先来两张效果图
1.新建attrs.xml,设置好自定义属性(其实就是右边隐藏布局的宽度啦),代码很简单,直接贴上来了
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="slidingitemlistview"> <attr name="right_width" format="dimension"></attr> </declare-styleable> </resources>
2.继承listview实现我们自己想要的效果~
(1)第一步在构造方法里获取自定义的宽度(右边局部隐藏的宽度)
public SlidingItemListView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.slidingitemlistview); mRightViewWidth = (int) typedArray.getDimension( R.styleable.slidingitemlistview_right_width, 200); typedArray.recycle(); }
(2)重写onInterceptTouchEvent()和onTouchEvent()方法,在ACTION_DOWN里获取mCurrentItemView,mPreItemView,mFirstX,mFirstY等。ACTION_UP里对是否在展示做简单的判断,在显示则隐藏。
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { float lastX = ev.getX(); float lastY = ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mIsHorizontal = null; mFirstX = lastX; mFirstY = lastY; int position = pointToPosition((int) mFirstX, (int) mFirstY); if (position >= 0) { View view = getChildAt(position - getFirstVisiblePosition()); mPreItemView = mCurrentItemView; mCurrentItemView = view; } Log.i("TAG", "onInterceptTouchEvent----->ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: Log.i("TAG", "onInterceptTouchEvent----->ACTION_UP"); /**点击隐藏布局会执行MotionEvent.ACTION_UP*/ if (mIsShown) { hideRightView(mCurrentItemView); } break; default: break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { float lastX = ev.getX(); float lastY = ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i("TAG", "onTouchEvent---->ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: float dx = lastX - mFirstX; float dy = lastY - mFirstY; Log.i("TAG", "onTouchEvent---->ACTION_MOVE"); if (mIsHorizontal == null) { if (!judgeScrollDirection(dx, dy)) { // 没判断出方向 break; } } if (mIsHorizontal) { if (mIsShown&&mPreItemView!=mCurrentItemView) { //正在展示,前视图不等于后视图 //则隐藏前视图 hideRightView(mPreItemView); } // 在mPreItemView!=mCurrentItemView执行 显示隐藏的宽度 if (dx < 0 && dx > -mRightViewWidth) { Log.i("TAG", "onTouchEvent---->MOVE -dx=" + -dx); mCurrentItemView.scrollTo((int) (-dx), 0); } // return true; } else { if (mIsShown) { //竖直方向滚动 //则隐藏前视图 hideRightView(mPreItemView); } } break; case MotionEvent.ACTION_UP: if (mIsShown) { //点击时如果有在显示的View //则隐藏前视图 Log.i("TAG", "MotionEvent.ACTION_UP 隐藏前视图"); // hideRightView(mCurrentItemView); hideRightView(mPreItemView); } if (mIsHorizontal != null && mIsHorizontal) { if (mFirstX - lastX > mRightViewWidth / 2) { showRight(mCurrentItemView); } else { // 不到一半则隐藏 hideRightView(mCurrentItemView); } Log.i("TAG", "成功接管OnTouchEvent CANCLE return TRUE"); return true; } break; default: break; } return super.onTouchEvent(ev); } /** * 展示隐藏的布局 * @param mCurrentItemView2 */ private void showRight(View mCurrentItemView2) { mCurrentItemView2.scrollTo(mRightViewWidth, 0); mIsShown = true; } /**隐藏布局*/ private void hideRightView(View mCurrentItemView2) { mCurrentItemView2.scrollTo(0, 0); mIsShown = false; }
这里面涉及到一个方法judgeScrollDirection,判断滑动方向,我是这么判断的
<span style="white-space:pre"> </span>/** * @param 水平距离差 * @param 竖直距离差 * @return 水平滑动或者竖直滑动都返回true 没有判断出滑动方向则返回false */ private boolean judgeScrollDirection(float dx, float dy) { if (Math.abs(dx) > 30 && Math.abs(dx) > Math.abs(dy) * 2) { mIsHorizontal = true; return true; } if (Math.abs(dy) > 30 && Math.abs(dy) > Math.abs(dx) * 2) { mIsHorizontal = false; return true; } return false; }
(3)啊,最后还有一个get setRightViewWidth方法不要我忘了,后面实例化adapter时还要用
<span style="white-space:pre"> </span>public int getRightViewWidth() { return mRightViewWidth; } public void setRightViewWidth(int mRightViewWidth) { this.mRightViewWidth = mRightViewWidth; }
自定义listview到此就大功告成了,是不是直接就可以使用了呢?我很负责任的告诉你:绝对不可以!adapter表示自己不乐意!
下面就来写一个adapter吧。继承BaseAdapter重写getCount,getItem,getItemId,getView。当然最重要的是getView。这些比较简单,直接贴代码了
private Context mContext; private LayoutInflater mInflater; private List<SlidingItembean> list; private int mRightViewWidth; public SlidingItemListViewAdapter(Context mContext, List<SlidingItembean> list, int mRightViewWidth) { super(); this.mContext = mContext; this.list = list; this.mRightViewWidth = mRightViewWidth; mInflater = LayoutInflater.from(mContext); } @Override public int getCount() { // TODO Auto-generated method stub return list.size(); } @Override public Object getItem(int position) { // TODO Auto-generated method stub return list.get(position); } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; onClick listener; if (convertView == null) { convertView = mInflater.inflate(R.layout.item_sliding_listview, null); viewHolder = new ViewHolder(); listener = new onClick();// 实例化 viewHolder.Re_left = (RelativeLayout) convertView .findViewById(R.id.Re_left); viewHolder.ll_right = (LinearLayout) convertView .findViewById(R.id.ll_right); viewHolder.num = (TextView) convertView .findViewById(R.id.tv_num_Re_left); viewHolder.name = (TextView) convertView .findViewById(R.id.tv_name_Re_left); viewHolder.path = (TextView) convertView .findViewById(R.id.tv_path_Re_left); viewHolder.play = (ImageView) convertView .findViewById(R.id.img_play_Re_left); viewHolder.setTop= (TextView) convertView.findViewById(R.id.tv_setTop); viewHolder.ll_delete = (LinearLayout) convertView .findViewById(R.id.ll_delete_ll_right); viewHolder.ll_setTop = (LinearLayout) convertView .findViewById(R.id.ll_setTop_ll_right); viewHolder.ll_setTop.setOnClickListener(listener);// 监听 viewHolder.ll_delete.setOnClickListener(listener);// 监听 viewHolder.play.setOnClickListener(listener);// 监听 convertView.setTag(viewHolder.play.getId(), listener);// 设置tag convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); listener = (onClick) convertView.getTag(viewHolder.play.getId());// 获取实例 } listener.setPosition(position);// 传递position // 设置布局参数 LayoutParams lp_left = new LayoutParams( android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.MATCH_PARENT); viewHolder.Re_left.setLayoutParams(lp_left); LayoutParams lp_right = new LayoutParams(mRightViewWidth, android.widget.LinearLayout.LayoutParams.MATCH_PARENT); viewHolder.ll_right.setLayoutParams(lp_right); SlidingItembean slidingItembean = list.get(position); viewHolder.num.setText(slidingItembean.getNum()); viewHolder.name.setText(slidingItembean.getName()); viewHolder.path.setText(slidingItembean.getPath()); viewHolder.setTop.setText(slidingItembean.getSetTop()); return convertView; }
static class ViewHolder { RelativeLayout Re_left; LinearLayout ll_right; LinearLayout ll_delete; LinearLayout ll_setTop; TextView num; TextView name; TextView path; ImageView play; TextView setTop; } }
细心的同学可能会发现onClick 对象,listener。这个listener是干什么的呢?原来啊这是个继承OnClickListener的类,目的和ViewHolder一样,复用item。以前只是复用item控件,这下连监听事件都可以复用了,嘿嘿。代码是不会骗人的,来看看这个Onclick类吧
class onClick implements OnClickListener { int position; public void setPosition(int position) { this.position = position; } @Override public void onClick(View v) { switch (v.getId()) { case R.id.img_play_Re_left: Toast.makeText(mContext, "play--->position=" + position, Toast.LENGTH_SHORT).show(); break; case R.id.ll_delete_ll_right: list.remove(position); SlidingItemListViewAdapter.this.notifyDataSetChanged(); break; case R.id.ll_setTop_ll_right: if (mySetTopInterface!=null) { mySetTopInterface.Onclick_ll_setTop_ll_right(v,position); }else { Toast.makeText(mContext, "mySetTopInterface==null", Toast.LENGTH_SHORT).show(); } break; default: break; } } }
这里用到了一个自己定义的接口MySetTopInterface,作用显而易见,设置置顶的时候调用此接口,传递两个参数,一个是被点击的View,另一个是position。
MySetTopInterface mySetTopInterface; public interface MySetTopInterface { void Onclick_ll_setTop_ll_right(View view,int position); } public void setMySetTopInterface(MySetTopInterface mySetTopInterface) { this.mySetTopInterface = mySetTopInterface; }
adapter表示自己作用已完成,等待领导指示!
领导表示listview的item布局忘贴上来了,,,
下面贴item_sliding_listview布局,,,
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="55dp" android:background="#fff" android:orientation="horizontal" > <RelativeLayout android:id="@+id/Re_left" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/tv_num_Re_left" android:layout_width="20dp" android:layout_height="20dp" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:background="@drawable/tv_num_bg" android:gravity="center" android:text="1" android:textColor="#fff" android:textSize="12sp" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toRightOf="@id/tv_num_Re_left" > <TextView android:id="@+id/tv_name_Re_left" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="5dp" android:text="《好久不见》" android:textColor="#000" android:textSize="16sp" /> <TextView android:id="@+id/tv_path_Re_left" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/tv_name_Re_left" android:text="/var/mobile/Contalners/Application" android:textSize="10sp" /> </RelativeLayout> <ImageView android:id="@+id/img_play_Re_left" android:layout_width="30dp" android:layout_height="30dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="10dp" android:scaleType="fitXY" android:src="@drawable/wechat_icon" /> </RelativeLayout> <LinearLayout android:id="@+id/ll_right" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" > <LinearLayout android:id="@+id/ll_delete_ll_right" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="#F77D48" android:gravity="center" android:orientation="vertical" android:padding="5dp" > <ImageView android:layout_width="30dp" android:layout_height="30dp" android:scaleType="fitXY" android:src="@drawable/del_icon_normal" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="删除" android:textColor="#fff" android:textSize="16sp" /> </LinearLayout> <LinearLayout android:id="@+id/ll_setTop_ll_right" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="#FED33F" android:gravity="center" android:orientation="vertical" android:padding="5dp" > <ImageView android:layout_width="30dp" android:layout_height="30dp" android:scaleType="fitXY" android:src="@drawable/qq_icon" /> <TextView android:id="@+id/tv_setTop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="置顶" android:textColor="#fff" android:textSize="12sp" /> </LinearLayout> </LinearLayout> </LinearLayout>
至此,listview的初始化算完事了。
下面来看看怎么应用吧(实现item置顶,取消置顶)
(1)在activity_main.xml里添加自定义的listview。其中 xmlns:dyk="http://schemas.android.com/apk/res/com.example.qqslidingitem"为自定义命名空间
<com.example.qqslidingitem.SlidingItemListView xmlns:dyk="http://schemas.android.com/apk/res/com.example.qqslidingitem" android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#fff4f7f9" android:cacheColorHint="#00000000" android:divider="#dddbdb" android:dividerHeight="1dp" dyk:right_width="120dp" />
(2)接下来就该实现刚留的MySetTopInterface接口,复写Onclick_ll_setTop_ll_right方法。(初始化工作直接贴代码)
<span style="white-space:pre"> </span>private SlidingItemListView mListView; private SlidingItemListViewAdapter adapter; private List<SlidingItembean> list = new ArrayList<SlidingItembean>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); initView(); initData(); initEvent(); } private void initEvent() { adapter = new SlidingItemListViewAdapter(MainActivity.this, list, mListView.getRightViewWidth()); mListView.setAdapter(adapter); adapter.setMySetTopInterface(this); // mListView.setSelection(position); mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, "item onclick " + list.get(position).getNum(), Toast.LENGTH_SHORT).show(); } }); } private void initData() { for (int i = 0; i < 50; i++) { SlidingItembean slidingItembean = null; if (i % 3 == 0) { slidingItembean = new SlidingItembean(String.valueOf(i), "你会不会忽然的出现", "/var/mobile/Contalners/Application", "置顶"); } else if (i % 3 == 1) { slidingItembean = new SlidingItembean(String.valueOf(i), "在街角的咖啡店", "/var/mobile/Contalners/Application", "置顶"); } else { slidingItembean = new SlidingItembean(String.valueOf(i), "我会带着笑脸,和你,坐着聊聊天", "/var/mobile/Contalners/Application", "置顶"); } list.add(slidingItembean); } } /** * 初始化界面 */ private void initView() { mListView = (SlidingItemListView) findViewById(R.id.listview); }
接下来重点完成置顶和取消置顶功能~
@Override public void Onclick_ll_setTop_ll_right(View view, int position) { if (list.get(position).getSetTop().equals("置顶")) { setTop(position); } else if (list.get(position).getSetTop().equals("取消置顶")) { unSetTop(position); } }
置顶setTop
<span style="white-space:pre"> </span>/** * 置顶 * @param position */ private void setTop(int position) { list.get(position).setSetTop("取消置顶"); list.add(0, list.get(position)); // 置顶后list.size增加一 所以要position+1 list.remove(position + 1); adapter.notifyDataSetChanged(); }
取消置顶我采用的策略是先遍历datalist,然后找到前一项比他小后一项比他大的位置然后插入。细心的同学会发现这样写有一个小小的bug要是选择项就是最小的没法处理,所以会有些特殊情况要单独拿出来讨论。
<span style="white-space:pre"> </span>/** * 取消置顶 * @param position */ private void unSetTop(int position) { boolean isAdd = false; /** 差值 */ int min = 9999999; /** 当前position的数值 */ int num; // 差值最小处的行数 int j = 0; int num2 = 0; int jumpNum = 0; list.get(position).setSetTop("置顶"); num = Integer.parseInt(list.get(position).getNum()); // list长度为2特殊处理 if (list.size() == 2) { // 第一行确定为取消置顶 if (list.get(1).getSetTop().equals("取消置顶")) { if (position == 0) { if (num == 0) { list.add(2, list.get(position)); } if (num == 1) { list.add(2, list.get(position)); } list.remove(position); adapter.notifyDataSetChanged(); } else { list.add(2, list.get(position)); list.remove(position); adapter.notifyDataSetChanged(); } } else { if (num == 0) { list.add(1, list.get(position)); } if (num == 1) { list.add(2, list.get(position)); } list.remove(position); adapter.notifyDataSetChanged(); } } else { for (int i = 0; i < list.size(); i++) { if (num > Integer.parseInt(list.get(i).getNum()) && num < Integer.parseInt(list.get(i + 1).getNum())) { list.add(i + 1, list.get(position)); isAdd = true; break; } } // 如果没有比自己小的值 例如0 则isAdd=false // 遍历list 寻找差值最小的地方插入list if (!isAdd) { for (int i = 0; i < list.size(); i++) { if (i == position || list.get(i).getSetTop().equals("取消置顶")) { // 排除与自身相比较 // 排除置顶item比较 Log.i("TAG", "调过" + i); jumpNum++; if (jumpNum == list.size()) { j = list.size(); } continue; } num2 = Integer.parseInt(list.get(i).getNum()); if (num2 - num < min) { min = num2 - num; // 记录行号 j = i; Log.i("TAG", "插入行数J=" + j); } } // 遍历完成后拿到差值min int number = min + num; list.add(j, list.get(position)); Log.i("TAG", "*********插入行数J=" + j); } list.remove(position); adapter.notifyDataSetChanged(); } }
大功告成!接下来做一个小总结吧。首先是自定义属性,其次是对布局隐藏的处理,第三是对getView中item的复用,最后是对自定义接口中删除,置顶,取消置顶功能实现的处理。第一次写的博客,不好的地方请谅解。
源码下载:http://download.csdn.net/detail/qq_17250009/9228877