第一次写自定义控件,虽然耦合度有点高,但是还是记录一下。
先来看一下效果
第一步我打算把两个选项挤到屏幕外面,这样向左滑动的时候可以出现图中的效果。
<?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="match_parent"
android:orientation="horizontal" >
<ImageView
android:id="@+id/img"
android:layout_width="50dp"
android:layout_height="50dp"
android:padding="5dp" />
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:text="content"
android:textColor="#000000"
android:textSize="18sp" />
<TextView
android:id="@+id/open"
android:layout_width="90dp"
android:layout_height="match_parent"
android:background="#c9c9ce"
android:gravity="center"
android:text="Open"
android:textColor="#ffffff"
android:textSize="18sp" />
<ImageView
android:id="@+id/delete"
android:layout_width="90dp"
android:layout_height="match_parent"
android:background="#f93f25"
android:scaleType="center"
android:src="@drawable/ic_delete"
android:textColor="#ffffff" />
</LinearLayout>
然后先是准备测试用的数据和适配器
public class MainActivity extends Activity implements OnItemClickListener {
private AppAdapter mAdapter;
private List<ApplicationInfo> mAppList;
private SlideListView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAppList = getPackageManager().getInstalledApplications(0);
lv=(SlideListView) findViewById(R.id.listView);
mAdapter=new AppAdapter(mAppList,this,lv);
lv.setAdapter(mAdapter);
lv.setList(mAppList);
lv.setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO Auto-generated method stub
Log.i("ShunXu","onItemClick");
}
}
AppAdapter.java:
public class AppAdapter extends BaseAdapter {
private List<ApplicationInfo> mAppList;
private Context context;
private SlideListView lv;
/**
* @param mAppList
* @param context
*/
public AppAdapter(List<ApplicationInfo> mAppList, Context context,SlideListView lv) {
super();
this.mAppList = mAppList;
this.context = context;
this.lv=lv;
}
@Override
public int getCount() {
return mAppList.size();
}
@Override
public ApplicationInfo getItem(int position) {
return mAppList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.list_item, null);
new ViewHolder(convertView);
}
final ViewHolder holder = (ViewHolder) convertView.getTag();
ApplicationInfo item = getItem(position);
holder.iv_icon.setImageDrawable(item.loadIcon(context.getPackageManager()));
holder.open.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
lv.clearScroll();
}
});
holder.tv_name.setText(item.loadLabel(context.getPackageManager()));
holder.delete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
lv.removeAndClearScroll();
}
});
return convertView;
}
class ViewHolder {
ImageView iv_icon,delete;
TextView tv_name,open;
public ViewHolder(View view) {
iv_icon = (ImageView) view.findViewById(R.id.img);
tv_name = (TextView) view.findViewById(R.id.tv);
delete=(ImageView) view.findViewById(R.id.delete);
open=(TextView) view.findViewById(R.id.open);
view.setTag(this);
}
}
}
SlideLiseView.java
public class SlideListView extends ListView {
/**
* 滑动类
*/
private Scroller scroller;
/**
* 当前项
*/
private int currentPos;
/**
* 手指按下的位置
*/
private int xDown;
/**
* 手指按下的位置
*/
private int yDown;
/**
* listview的item
*/
private View item;
/**
* 是否是滑开状态
*/
private boolean isOpen = false;
/**
* 滑动临界值
*/
private int minWidth;
/**
* listview中的list
*/
private List<ApplicationInfo> list;
/**
* listview的适配器
*/
private AppAdapter adapter;
public SlideListView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
scroller = new Scroller(context);
minWidth = dip2px(context, 90);
}
public List<ApplicationInfo> getList() {
return list;
}
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
public void setList(List<ApplicationInfo> list) {
this.list = list;
}
public AppAdapter getAdapter() {
return adapter;
}
@Override
public void setAdapter(ListAdapter adapter) {
// TODO Auto-generated method stub
super.setAdapter(adapter);
this.adapter = (AppAdapter) adapter;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i("ShunXu", "dispatchTouchEvent");
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("ShunXu", "dispatchTouchEvent" + "----ACTION_DOWN");
// 初始化点击位置
xDown = (int) ev.getX();
yDown = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.i("ShunXu", "dispatchTouchEvent" + "----ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i("ShunXu", "dispatchTouchEvent" + "----ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.i("ShunXu", "onInterceptTouchEvent");
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("ShunXu", "onInterceptTouchEvent" + "----ACTION_DOWN");
int pos = pointToPosition(xDown, yDown);
// 如果item为null的话,肯定是第一次进来还没有点击
if (item == null) {
isOpen = false;
}
// 滑开状态之后的点击有两种情况,一种是又点击自身,一种是点击别的item了
if (isOpen) {
if (pos != currentPos) {
MotionEvent cancelEvent = MotionEvent.obtain(ev);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
clearScroll();
currentPos = pos;
// 点击别的item就把原来的item复位,复位之后取消本次ontouch事件,不让它向下传递
onTouchEvent(cancelEvent);
return false;
} else {
return false;
}
}
// 通过点击位置拿到item在list中的位置
currentPos = pointToPosition(xDown, yDown);
if (currentPos == AdapterView.INVALID_POSITION) {
return false;
}
// 注意这里要减去getFirstVisiblePosition(),因为getChildAt函数拿到的
// position的范围是当前屏幕可见item个数,所以如果直接写item在list中的绝对位置
// 会导致后面的item拿不到,
item = getChildAt(currentPos - getFirstVisiblePosition());
break;
case MotionEvent.ACTION_MOVE:
Log.i("ShunXu", "onInterceptTouchEvent" + "----ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i("ShunXu", "onInterceptTouchEvent" + "----ACTION_UP");
break;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.i("ShunXu", "onTouchEvent");
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("ShunXu", "onTouchEvent" + "----ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i("ShunXu", "onTouchEvent" + "----ACTION_MOVE");
int dx = (int) (xDown - ev.getX());
if (dx <= minWidth * 2) {
// 手指向左滑
if (dx > 0) {
if (item != null) {
item.scrollTo(dx, 0);
}
} else if(isOpen){
item.scrollTo(start+dx, 0);
}
}
break;
case MotionEvent.ACTION_UP:
Log.i("ShunXu", "onTouchEvent" + "----ACTION_UP");
int distance = (int) (xDown - ev.getX());
if (distance > 50) {
if (distance >= minWidth) {
scroller.startScroll(item.getScrollX(), 0, minWidth * 2 - item.getScrollX(), 0, 300);
postInvalidate();
isOpen = true;
} else if (distance < minWidth) {
clearScroll();
}
}else if(distance<0&&isOpen){
//如果手指向右滑动距离小于一个选项的宽度,就让他回复open状态
if (distance > -minWidth) {
//从当前位置退回滑动距离即复原open状态
//View.getScrollX()return的mScrollX,始终是View的内容与View左边缘的距离
//所以当我们向右滑动的时候,其实是把mScrollX变小了,所以这里要用-distance把mScrollX变大
//之前忘了这一点,通过试参数试出来效果,然后又去看了下书才想起来getScrollX()的含义
scroller.startScroll(item.getScrollX(), 0, -distance, 0, 300);
}
//如果滑动距离大于一个选项宽度,就把选项关闭
else if (distance < -minWidth) {
clearScroll();
}
}
break;
}
return super.onTouchEvent(ev);
}
int start;
/**
* 配合Scroller实现View的平滑滚动
*/
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
if (item != null) {
item.scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
start = item.getScrollX();
}
}
}
/**
* 选项回归原位
*/
public void clearScroll() {
if (item != null) {
scroller.startScroll(item.getScrollX(), 0, -item.getScrollX(), 0, 300);
}
postInvalidate();
isOpen = false;
}
/**
* 点击删除按钮,执行删除动画及list的删除/adapter的更新
*/
public void removeAndClearScroll() {
if (item != null) {
clearScroll();
final int height = item.getMeasuredHeight();
Animation animation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
ViewGroup.LayoutParams layoutParams = item.getLayoutParams();
layoutParams.height = height - (int) (height * interpolatedTime);
item.setLayoutParams(layoutParams);
}
};
animation.setDuration(300);
animation.setAnimationListener(listener);
startAnimation(animation);
}
}
AnimationListener listener = new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationEnd(Animation animation) {
// TODO Auto-generated method stub
list.remove(currentPos);
adapter.notifyDataSetChanged();
isOpen = false;
}
};
}
参考
https://github.com/yydcdut/SlideAndDragListView
app下载地址
https://github.com/yydcdut/SlideAndDragListView/blob/master/apk/sdlv.apk?raw=true
本来是想看人家大神的代码的,但是看了半天看不懂O.O,只好自己写了一个,这是我的第一个自定义控件,之前一直觉得自定义控件好难,现在总算是迈出了第一步了。加油!