1. 程式人生 > >Android實現ListView阻尼式(下拉回彈)效果

Android實現ListView阻尼式(下拉回彈)效果

最近想模仿小米MIUI V5簡訊裡面的一個功能——私密簡訊,它的入口在簡訊列表,列表往下拉到1/3左右,我用Eclipse上的工具截了圖,包括該結構的佈局,如下圖:


從它的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>


上面就是主要的實現程式碼了,接下來看一下效果圖:


弱弱的問一下我上傳的gif圖片不動,這是為啥呢。。以前沒上傳過

公司程式碼加了密,原始碼就不上傳了。。。

Android學習中。。。