前言
打算做个阅读类的 App,想要模仿豆瓣一刻的上拉翻页效果。做了一些 search,发现跟 ListView 的 PullToRefresh 实现是类似的。而后者已经用过很多次了,还是不会改造。所以,尽管实现的不够完善,还是要自己动手,否则永远不明白其中的原理。从最简单的做起,先完成上拉翻页的效果。自己动手系列的custom view 都不是拿来用的,因为很多情况没有考虑,只把最基本的功能实现了,使得代码量很小,从而看得出原理。
参考链接
Android-PullToRefresh
Android 下拉刷新框架实现
PullToRefreshView
效果图
原理
所谓上拉翻页,其实是上拉触发翻页。上拉用 PullToRefresh 实现,翻页用 fragment transition animation 实现。简单实现上拉翻页,只需要做两点:
- 设置 footer view 的高度 programmly
- 随手指移动 scroll your custom view
关键源码
public class PullToTurnPageLayout extends LinearLayout {
private final static float SCROLL_RATIO = 0.35f;
private View contentView;
private View footerView;
private float lastY;
private ScrollView scrollView;
private int footerViewHeight;
private TextView footerTextView;
public PullToTurnPageLayout(Context context) {
super(context);
}
public PullToTurnPageLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PullToTurnPageLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
contentView = getChildAt(0);
footerView = getChildAt(1);
footerView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
footerViewHeight = footerView.getMeasuredHeight();
LayoutParams layoutParams = (LayoutParams) footerView.getLayoutParams();
layoutParams.height = footerViewHeight;
footerView.setLayoutParams(layoutParams);
scrollView = (ScrollView) contentView;
footerTextView = (TextView) footerView;
}
/**
* should get touch event from here, otherwise you can not scroll out the footerView closely after scroll the scrollview
* @param event
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (scrollView.getScrollY() >= scrollView.getChildAt(0).getMeasuredHeight()- scrollView.getHeight()){
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
float dy = event.getY() - lastY;
scrollBy(0, (int) (-dy*SCROLL_RATIO));
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (getScrollY() >= footerViewHeight){
if (footerListener != null){
footerListener.onTrigger();
}
} else {
scrollTo(0, 0);
}
break;
}
if (getScrollY() >= footerViewHeight){
footerTextView.setText("松开翻页");
} else {
footerTextView.setText("上拉翻页");
}
lastY = event.getY();
}
return super.dispatchTouchEvent(event);
}
private FooterListener footerListener;
public void setFooterListener(FooterListener footerListener) {
this.footerListener = footerListener;
}
public static interface FooterListener {
public void onTrigger();
}
}
不理解的地方
先用LinearLayout 做的实验
<?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="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Medium Text"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginTop="-100dp" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="a lot of text, leave out here"
android:textAppearance="?android:attr/textAppearanceLarge" />
</ScrollView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Medium Text"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
非常不理解的是,在 ScrollView 内容很多的情况下,底部 TextView的 layout_height 设为 wrap_content 的话,永远显示不出来,即便设置顶部 TextView 的 topMargin 为负也不行。可能是因为View Tree 会判断没有底部 TextView 的位置了,wrap_content 也没用。所以手动设置 footerView 的 height 非常重要。
弯路
- 因为没有给 PullToTurnPage 继承的 LinearLayout设置 orientation,footer view 一直显示不出来
TODO
- 解决先上拉再下拉时的 bug
- 加入箭头动画
- 加入下拉翻页