无图无真相,上图。
图片太大了,截掉了点。
界面很简单,就两个view。
咳咳
首先,要想很容易的理解这部分的代码,应该熟悉android的消息派发机制(尤其是dispatchTouchEvent、onTouchEvent这两个方法),自顶向下树形分发,我转载的上一篇文章对此写的清楚明白,超级赞。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="li.slidelist.MainActivity">
<li.slidelist.SlideListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"></li.slidelist.SlideListView>
</LinearLayout>
主布局很简单,就是上面那样,SlideListView就是我写的listview。然后是listview的每个item的布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:layout_width="match_parent"
android:layout_height="60dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="拉我"
android:id="@+id/textView"
android:gravity="center"
android:layout_alignParentLeft="true" />
<Button
android:layout_width="wrap_content"
android:layout_height="60dp"
android:text="叫我?"
android:id="@+id/button"
android:layout_alignParentRight="true"
android:visibility="gone"/>
</RelativeLayout>
通过visibility属性看得出来button是隐藏的。这里说明一下,布局不能套的很多,根布局下面最好直接放item要用的组件,如果用各种布局套来套去,会使树(整个xml文件就是一个树)变得复杂,这和下面的滑动实现有很大的关系。
看最重要的代码(关键地方的注释非常详细):
package li.slidelist;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
/**
* Created by Lee Y on 2016/5/19.
*/
public class SlideListView extends ListView{
/*
代表每个item的根布局
*/
private ViewGroup itemView;
/*
代表被隐藏滑动要显示出来的view
*/
private View appearView;
/*
代表需要滑动的view
*/
private View slideView;
/*
downx 代表 手指刚按下的x坐标
downy 代表 手指刚按下的y坐标
坐标不懂得可以去百度下,很简单。
currentPosition 代表 手指按住的是listview中的哪个item
*/
private int downX, downY, currentPosition;
/*
代表屏幕宽度
*/
private int srceenWidth;
/*
构造方法不解释,固定的
*/
public SlideListView(Context context){
this(context, null);
}
public SlideListView(Context context, AttributeSet attr){
this(context, attr, 0);
}
public SlideListView(Context context, AttributeSet attr, int defStyle){
super(context, attr, defStyle);
}
/*
设置屏幕宽度
*/
public void setSrceenWidth(int s){
srceenWidth = s;
}
/*
listview的消息分派方法(实际上是View的),返回true则消息分派到此结束(不会再往下寻找),消息由该listview来处理
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
downX = (int) ev.getX();
downY = (int) ev.getY();
currentPosition = pointToPosition(downX, downY);
/*
下面的三句非常重要,之前做过实验,通过自写Adapter然后将需要显示的view传过来以便响应滑动事件,可是发现穿过来的view(就是要出现的按钮)
显示的位置是不对的(比如:滑动第三个item,应该在第三个item后面出现,可确实在第六个或第八个item后面出现,很奇葩),不光是这一个问题
我想滑动的是布局里面的那个TextView而不是整个布局,如果不分别获得TextView和Button的对象引用,那么滑动将是整个item(这达不到图上的效果)
因此我想到的办法,就是用viewgroup对象把textview和button对象索引出来,viewgroup对象就是下面的itemView(可以把它看成每个item的根布局)。
这样问题也就随之而来了,如果item的布局过于复杂单靠根布局对象去寻找需要的view对象引用是很费力的。其实这也有解决办法,这里先不写了,
注释有点长了=。=
*/
itemView = (ViewGroup) getChildAt(currentPosition - getFirstVisiblePosition());
slideView = itemView.getChildAt(0); // item中的textview
appearView = itemView.getChildAt(1); // item中被隐藏的button
appearView.setOnClickListener(new OnClickListener() {
/*
button点击事件,让自己消失,并且使textview恢复原位
*/
@Override
public void onClick(View v) {
v.setVisibility(View.GONE);
slideView.scrollTo(0,0);
}
});
break;
}
return super.dispatchTouchEvent(ev);
}
/*
listview的触摸消息处理方法,返回true则此次触摸事件到此结束,这里注意此方法里面并没有处理listview纵向滑动的代码,
如果返会true,listview的纵向滑动会失效,所以应该最后调用父方法
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
// item的位置是否合法
if(currentPosition == AdapterView.INVALID_POSITION){
return super.onTouchEvent(ev);
}
break;
case MotionEvent.ACTION_MOVE:
// 通过手指移动的x坐标 计算textview滑动的距离
int x = (int)ev.getX();
int moveX = downX - x;
downX = x;
slideView.scrollBy(moveX, 0);
break;
case MotionEvent.ACTION_UP:
// 如果左滑超过屏幕的三分之一就显示button,右滑超过三分之一隐藏button,如果都不是怎么滑动都会恢复原位
if(slideView.getScrollX() > srceenWidth/3){
appearView.setVisibility(View.VISIBLE);
}else if(slideView.getScrollX() > -srceenWidth/3){
appearView.setVisibility(View.GONE);
slideView.scrollTo(0,0);
}else{
slideView.scrollTo(0,0);
}
break;
}
return super.onTouchEvent(ev);
}
}
代码并没有添加根据速度的大小显示按钮的代码,所以怎么快速滑动都不会出现按钮(除非满足上面的条件)。
package li.slidelist;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
public class MainActivity extends Activity{
private SlideListView slideListView;
private MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
slideListView = (SlideListView) findViewById(R.id.listview);
slideListView.setSrceenWidth(getWindowManager().getDefaultDisplay().getWidth());
adapter = new MyAdapter(this, R.layout.item);
slideListView.setAdapter(adapter);
}
}
再附上主活动代码
package li.slidelist;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.TextView;
/**
* Created by Lee Y on 2016/5/19.
*/
public class MyAdapter extends BaseAdapter{
private Context context;
private int rescourseID;
private ViewHolder holder;
public MyAdapter(Context c, int r){
context = c;
rescourseID = r;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public int getCount() {
return 15;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
holder = null;
if(convertView == null){
holder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(rescourseID, null);
holder.v = (TextView) convertView.findViewById(R.id.textView);
holder.b = (Button) convertView.findViewById(R.id.button);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
return convertView;
}
private class ViewHolder{
TextView v;
Button b;
}
}
好吧,还有适配器的代码,不过适配器用系统自带的就可以。
把速度代码添加上去也是很简单的,这里就不写了。
但是代码是有瑕疵的,主要是由于事件判断不严谨,纵向滑动时有可能横向滑动也会跟着动。解决办法也很容易, 把它们互相屏蔽就可以了。
总共三类事件,点击事件、纵向滑动事件、横向滑动事件,让他们彼此互不相见,办法很简单。首先,在滑动时屏蔽点击事件:
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
//只要按钮处于消失状态,就截断所有DOWN动作
if(appearView.getVisibility() == View.GONE){
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
只要重写onInterceptTouchEvent方法,并在DOWN动作时截断事件分发就可以了,这样写岂不是不是把ListView的单击事件弄没了?其实不会的,ListView的单击事件是响应在ListView的层次上,不会下发。
然后把纵向滑动和横向滑动分开,这只需置一个判断值:
private boolean isHorizontal = false;
// 横向滑动时 置为true
接着在分发MOVE动作的时候确定isHorizontal的值:
public boolean dispatchTouchEvent(MotionEvent ev) {
case MotionEvent.ACTION_MOVE:
int x = (int) ev.getRawX() - local[0];
int y = (int) ev.getRawY() - local[1];
// 下面是新添加的代码,判断是横向滑动还是纵向滑动
if(Math.abs(downX - x) > minDistance && Math.abs(downY - y) < minDistance){
isHorizontal = true;
}
break;
}
最后在执行动作的时候做出判断:
public boolean onTouchEvent(MotionEvent ev) {
if(isHorizontal) { // 在这里做出判断
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
Log.e("SlideList", "MOVE");
int x = (int) ev.getX();
int moveX = downX - x;
downX = x;
if(moveX > 0 || appearView.getVisibility() == VISIBLE){
slideView.scrollBy(moveX, 0);
}
break;
case MotionEvent.ACTION_UP:
Log.e("SlideList", "UP");
if (slideView.getScrollX() > srceenWidth / 3 ) {
appearView.setVisibility(View.VISIBLE);
} else if (slideView.getScrollX() > -srceenWidth / 3 ) {
appearView.setVisibility(View.GONE);
slideView.scrollTo(0, 0);
} else {
slideView.scrollTo(0, 0);
}
isHorizontal = false;
break;
}
return true; // 直接返回true 不去触发纵向滑动
}
return super.onTouchEvent(ev);
}
这样所有冲突就都被解决了。