Android实现ListView阻尼式(下拉回弹)效果

时间:2020-12-09 19:43:06

最近想模仿小米MIUI V5短信里面的一个功能——私密短信,它的入口在短信列表,列表往下拉到1/3左右,我用Eclipse上的工具截了图,包括该结构的布局,如下图:

Android实现ListView阻尼式(下拉回弹)效果


从它的UI结构可以看出,用的是层叠结构,即将ListView和一个普通View(取名叫privateEntry)层叠在一起,ListView里面有四个数据项,第0项和第3项都是FrameLayout,应该分别是添加的HeaderView(搜索框)和FooterView,FooterView的用途应该是为了填充屏幕,挡住平时隐藏的那个普通View(取名叫privateEntry)。

下面是我自己写的代码,借鉴了网上的BounceScrollView写的BounceListView,实现后的效果没有小米的那么好,有待改进。

首先看BounceListView.java,这是主要实现代码:

package com.lee.listviewdemo;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.GestureDetector.OnGestureListener;
import android.view.animation.TranslateAnimation;
import android.widget.ListView;

public class BounceListView extends ListView {
    private boolean outBound = false;
    private int distance;
    private int firstOut;
    private Context mContext;
    private BounceCallBack mBounceCallback;
    public static final String TAG = "BounceListView";
    private boolean isCalled = false; 

    public BounceListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
    }

    public BounceListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
    }

    public BounceListView(Context context) {
        super(context);
        mContext = context;
    }

    GestureDetector gestureDetector = new GestureDetector(mContext,
            new OnGestureListener() {
                @Override
                public boolean onSingleTapUp(MotionEvent e) {
                    // TODO Auto-generated method stub
                    Log.d(TAG, "I'am onSingleTapUp().");
                    return false;
                }

                @Override
                public void onShowPress(MotionEvent e) {
                    // TODO Auto-generated method stub
                    Log.d(TAG, "I'am onShowPress().");
                }

                @Override
                public boolean onScroll(MotionEvent e1, MotionEvent e2,
                        float distanceX, float distanceY) {
                    Log.e(TAG, "Entry onscroll distanceX = " + distanceX
                            + ", distanceY = " + distanceX);
                    int firstPos = getFirstVisiblePosition();

                    // outbound Top
                    if (outBound && firstPos != 0) {
                        scrollTo(0, 0);
                        return false;
                    }
                    View firstView = getChildAt(firstPos);
                    // View lastView = getChildAt(lastPos - 1);
                    if (!outBound) {
                        firstOut = (int) e2.getRawY();
                    }

                    if (firstView != null
                            && (outBound || (firstPos == 0
                                    && firstView.getTop() == 0 && distanceY < 0))) {
                        // Record the length of each slide
                        distance = firstOut - (int) e2.getRawY();
                        Log.e(TAG, "Lee--------distance = " + distance);
                        Log.e(TAG, "firstOut = " + firstOut + ", firstPos = "
                                + firstPos + ", firstView.getTop() = "
                                + firstView.getTop());
                        int tempdistance = 60 * (-distance) / 100; //为了增加下拉的难度
                        if (tempdistance > getHeight() / 2)
                            scrollTo(0, -getHeight() / 2);
                        else
                            scrollTo(0, -tempdistance);
                        if (mBounceCallback != null && shouldCallBack(tempdistance)) {
                            isCalled = true;
                            mBounceCallback.onBounceCallBack();
                        }
                        return true;
                    }

                    return false;
                }

                @Override
                public void onLongPress(MotionEvent e) {
                    // TODO Auto-generated method stub
                    Log.d(TAG, "I'am onLongPress().");
                }

                @Override
                public boolean onFling(MotionEvent e1, MotionEvent e2,
                        float velocityX, float velocityY) {
                    // TODO Auto-generated method stub
                    Log.d(TAG, "I'am onFling().");
                    return false;
                }

                @Override
                public boolean onDown(MotionEvent e) {
                    // TODO Auto-generated method stub
                    Log.d(TAG, "I'am onDown().");
                    return false;
                }
            });

    private boolean shouldCallBack(int tempdistance) {

        if ((tempdistance > getHeight() / 2) && !isCalled) { //下拉到ListView高度的一半就会触发事件
            Log.e(TAG, "shouldCallBack()");
            return true;
        }
        return false;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int act = event.getAction();
        if ((act == MotionEvent.ACTION_UP || act == MotionEvent.ACTION_CANCEL)
                && outBound) {
            outBound = false;
            isCalled = false;
            // scroll back
        }
        if (!gestureDetector.onTouchEvent(event)) {
            outBound = false;
        } else {
            outBound = true;
        }
        Rect rect = new Rect();
        getLocalVisibleRect(rect);
        Log.i(TAG, "rect.top = " + rect.top);
        TranslateAnimation am = new TranslateAnimation(0, 0, -rect.top, 0);
        am.setDuration(300);
        startAnimation(am);
        scrollTo(0, 0);
        
        return super.dispatchTouchEvent(event);
    }

    public void setOnBounceCallBack(BounceCallBack callback) {
        mBounceCallback = callback;
    }
    //回调触发接口
    public interface BounceCallBack {
        public void onBounceCallBack();
    }
}
再看main.xml的布局,这个很简单在FrameLayout中放置一个View和一个ListView:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/LinearLayout01"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    
    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/privateEntry"
        android:background="@drawable/surprise" >
    </View>
    
    <com.lee.listviewdemo.BounceListView
        android:id="@+id/MyListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#00ffff" />

</FrameLayout>

然后是Main.java,

package com.lee.listviewdemo;

import java.util.ArrayList;
import java.util.HashMap;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.SimpleAdapter;
import android.util.Log;
import android.view.View;

import com.lee.listviewdemo.R;
import com.lee.listviewdemo.BounceListView.BounceCallBack;

public class Main extends Activity {
	/** Called when the activity is first created. */
    private View footerView;
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		BounceListView list = (BounceListView) findViewById(R.id.MyListView);
		footerView = getLayoutInflater().inflate(R.layout.footer_container, null);
		ArrayList<HashMap<String, String>> mylist = new ArrayList<HashMap<String, String>>();
		for (int i = 0; i < 5; i++) {
			HashMap<String, String> map = new HashMap<String, String>();
			map.put("ItemTitle", "BounceListView.....");
			map.put("ItemText", "This is text.....");
			mylist.add(map);
		}
		//设置listview弹性下拉事件触发
		list.setOnBounceCallBack(new BounceCallBack() {
            
            @Override
            public void onBounceCallBack() {
                // TODO Auto-generated method stub
                Intent intent = new Intent(Main.this,
                        TestActivity.class);
                startActivity(intent);
                overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
            }
        });
		
		SimpleAdapter mSchedule = new SimpleAdapter(this, 
				mylist,
				R.layout.my_listitem,
				new String[] { "ItemTitle", "ItemText" },
				new int[] { R.id.ItemTitle, R.id.ItemText });
		//添加FooterView,当listview数据少时,可以填充屏幕
		list.addFooterView(footerView, null, false);
		list.setAdapter(mSchedule);
        Log.e(BounceListView.TAG, "footerview == " + footerView + "list.count == " + list.getCount());
	}
}

FooterView的布局,就是加一个layout,让其宽高自适应:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/footer_container" >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ffffff" />

</FrameLayout>



上面就是主要的实现代码了,接下来看一下效果图:

Android实现ListView阻尼式(下拉回弹)效果

弱弱的问一下我上传的gif图片不动,这是为啥呢。。以前没上传过

公司代码加了密,源代码就不上传了。。。

Android学习中。。。