啥也不說,先來個預覽圖,雖然有點卡:
代碼位址:https://github.com/dreamlizhengwei/DragTopLayout
如果覺得圖比較卡,可以搜一下DragTopLayout,git上有一個用的比較廣的,附位址:
https://github.com/chenupt/DragTopLayout
這個DragTopLayout比較複雜,而且存在一些bug,比如頭部的控件高度是變化的情況下,會存在一些問題,使用時還要借助EventBus,比較麻煩。
綜合以上原因,于是乎,自己寫了一個,隻有一個類,使用非常簡單。
下面先介紹一下基本用法:
這裡用了我的另一篇部落格裡的
ExpandableTextView
,感興趣的可以去參考一下
<RelativeLayout 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">
<!--DragTopLayout裡面放兩個Layout分别作為頭和内容的跟布局-->
<com.test.dragtoplayout.DragTopLayout
android:id="@+id/drag"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--頭的跟布局,裡面放頭部内容,我這裡放的是ExpandableTextView-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.test.dragtoplayout.ExpandableTextView
android:id="@+id/expand_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30"
android:textColor="#666666"
android:textSize="16sp" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@android:color/transparent" />
</com.test.dragtoplayout.ExpandableTextView>
</LinearLayout>
<!--内容布局,裡面放内容的,我這裡放的是一個ListView-->
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0f0"
android:orientation="vertical">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</LinearLayout>
</com.test.dragtoplayout.DragTopLayout>
</RelativeLayout>
Activity代碼:
public class MainActivity extends Activity {
private DragTopLayout drag;
private ListView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drag = (DragTopLayout) findViewById(R.id.drag);
lv = (ListView) findViewById(R.id.lv);
// 設定滾動的view,因為内容不居中可能有好幾個view,是以要手動指定誰是可滾動的布局
// 我這裡是ListView,還可以是ScrollView等
drag.setTargetView(lv);
lv.setAdapter(new BaseAdapter() {
@Override
public int getCount() {
return ;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return ;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView t = new TextView(MainActivity.this);
t.setText("==================== " +position);
return t;
}
});
}
}
整體使用就是這樣,so easy。
下面是DragTopLayout的代碼,非常簡潔,事件處理這塊用到了這篇部落格的分析SwipeRefreshLayout的事件處理
代碼中的注釋還是比較詳細的,單獨說幾點比較關鍵的:
-
方法是判斷可滾動View是否能向上滾動,例如ListView是否已經滾動到頂部,是否可以繼續向上滾動,這個方法參考了canChildScrollUp()
中的判定,是非常權威滴SwipeRefreshLayout
-
方法也是參考的requestDisallowInterceptTouchEvent()
中的寫法,至于為什麼,參考SwipeRefreshLayout的事件處理。SwipeRefreshLayout
- 最後一點就是
的onTouchEvent()
事件中的距離計算可能有點不好了解,這個就需要你對自定義View了解的比較深入才能明白了,如果說的比較透徹可能篇幅較大,各位童鞋單獨找資料學習吧 = =MotionEvent.ACTION_MOVE
- 最後一點,這種效果其實可以借助
來實作。。。。。。CoordinatorLayout
public class DragTopLayout extends FrameLayout {
public DragTopLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public DragTopLayout(Context context, AttributeSet attrs) {
this(context, attrs, );
}
public DragTopLayout(Context context) {
this(context, null);
}
private void init() {
mScroller = new Scroller(getContext().getApplicationContext());
}
/**
* 目前頭部可見還是滾出螢幕,true為滾出螢幕
*/
private boolean mCollapsed = false;
/**
* 頭部高度
*/
private int mHeadHeight;
/**
* 滾動輔助
*/
private Scroller mScroller;
/**
* 速度計算,每次用完要recycle
*/
private VelocityTracker velocityTracker;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
mHeadHeight = getChildAt().getMeasuredHeight();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
View v1 = getChildAt();
v1.layout(, , v1.getMeasuredWidth(), v1.getMeasuredHeight());
View v2 = getChildAt();
v2.layout(, v1.getMeasuredHeight(), getMeasuredWidth(),
getMeasuredHeight() + mHeadHeight);
}
/**
* 按下點,滑動過程中的上一個點
*/
private PointF mDownPoint = new PointF();
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownPoint.set(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
float y = event.getY();
// Y方向需要scrollBy的距離,正值表示需要向上滾動
float scrollByDelt = -y + mDownPoint.y;
// 假如按照計算的距離偏移後的偏移量,即getScrollY()的值
float realDelt = getScrollY() + scrollByDelt;
Log.e("lzw", "------> " + scrollByDelt + " " + realDelt);
if (realDelt < ) { // 表示按照實際的手指滑動距離向下移動的話太大,則直接計算出剛好頭部顯示出來的realDelt
scrollByDelt = - getScrollY();
} else if (realDelt > mHeadHeight) { // 同樣表示實際距離太大,計算出合适的距離
scrollByDelt = mHeadHeight - getScrollY();
}
scrollBy(, (int) scrollByDelt);
mDownPoint.set(event.getX(), y);
break;
case MotionEvent.ACTION_UP:
mDownPoint.set(, );
checkPosition();
break;
}
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.e("lzw", "onInterceptTouchEvent canChildScrollUp " + canChildScrollUp());
if (canChildScrollUp()) { // 能向上滾動,一定是滾動view處理事件
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownPoint.set(event.getX(), event.getY());
return false;
case MotionEvent.ACTION_MOVE:
// 橫向滾動大于縱向,也不響應
if (Math.abs(event.getX() - mDownPoint.x) > Math.abs(event.getY()
- mDownPoint.y)) {
mDownPoint.set(event.getX(), event.getY());
return super.onInterceptTouchEvent(event);
}
// 在向下移動
if (event.getY() > mDownPoint.y) {
mDownPoint.set(event.getX(), event.getY());
if (mCollapsed) { // 頭部不可見了,向下滾動需要攔截
return true;
} else {
return super.onInterceptTouchEvent(event);
}
}
// 在向上移動
if (event.getY() < mDownPoint.y) {
mDownPoint.set(event.getX(), event.getY());
if (mCollapsed) { // 頭部滾出螢幕,不攔截
return super.onInterceptTouchEvent(event);
} else {
return true;
}
}
mDownPoint.set(event.getX(), event.getY());
case MotionEvent.ACTION_UP:
// 檢查頭部是否移除去
mCollapsed = getScrollY() >= mHeadHeight;
mDownPoint.set(event.getX(), event.getY());
return super.onInterceptTouchEvent(event);
}
return true;
}
@Override
public void requestDisallowInterceptTouchEvent(boolean b) {
// if this is a List < L or another view that doesn't support nested
// scrolling, ignore this request so that the vertical scroll event
// isn't stolen
if ((android.os.Build.VERSION.SDK_INT < && mTarget instanceof AbsListView)
|| (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {
// Nope.
} else {
super.requestDisallowInterceptTouchEvent(b);
}
}
/**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
*/
public boolean canChildScrollUp() {
if (android.os.Build.VERSION.SDK_INT < ) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
return absListView.getChildCount() >
&& (absListView.getFirstVisiblePosition() > || absListView
.getChildAt().getTop() < absListView
.getPaddingTop());
} else {
return ViewCompat.canScrollVertically(mTarget, -)
|| mTarget.getScrollY() > ;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -);
}
}
/**
* 檢查是否需要關閉或者打開
*/
private void checkPosition() {
// 移出去的大小,不能直接在if裡面用,否則傳回值不正确
int opOffset = getScrollY();
if (opOffset < (mHeadHeight / )) {
open();
} else {
close();
}
}
/**
* 向上移動,隐藏頭部
*/
private void close() {
mCollapsed = true;
mScroller.startScroll(, getScrollY(), , mHeadHeight - getScrollY());
invalidate();
}
/**
* 向下移動,頭部出現
*/
private void open() {
mCollapsed = false;
mScroller.startScroll(, getScrollY(), , -getScrollY());
invalidate();
}
@Override
public void computeScroll() {
// 傳回值為boolean,true說明滾動尚未完成,false說明滾動已經完成。
if (mScroller.computeScrollOffset()) {
// 移動位置
scrollTo(, mScroller.getCurrY());
invalidate();
}
}
/**
* 可滾動view
*/
private View mTarget;
/**
* 設定可滾動view
*/
public void setTargetView(View v) {
mTarget = v;
}
}
以上,應該沒什麼大的bug,有bug大家留言 -_-