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

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

最近想模仿小米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学习中。。。