Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> DragTopLayout

DragTopLayout

編輯:關於Android編程

啥也不說,先來個預覽圖,雖然有點卡:
這裡寫圖片描述

代碼地址:https://github.com/dreamlizhengwei/DragTopLayout



如果覺得圖比較卡,可以搜一下DragTopLayout,git上有一個用的比較廣的,附地址:
https://github.com/chenupt/DragTopLayout
這個DragTopLayout比較復雜,而且存在一些bug,比如頭部的控件高度是變化的情況下,會存在一些問題,使用時還要借助EventBus,比較麻煩。

綜合以上原因,於是乎,自己寫了一個,只有一個類,使用非常簡單。
下面先介紹一下基本用法:
這裡用了我的另一篇博客裡的ExpandableTextView,感興趣的可以去參考一下



        
        
            
            
                

                    

                    
                
            

            
            
                
            
        

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 50;
            }

            @Override
            public Object getItem(int position) {
                return null;
            }

            @Override
            public long getItemId(int position) {
                return 0;
            }

            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                TextView t = new TextView(MainActivity.this);
                t.setText("==================== " +position);
                return t;
            }
        });
    }
}

整體使用就是這樣,so easy。

下面是DragTopLayout的代碼,非常簡潔

代碼中的注釋還是比較詳細的,單獨說幾點比較關鍵的:

canChildScrollUp()方法是判斷可滾動View是否能向上滾動,例如ListView是否已經滾動到頂部,是否可以繼續向上滾動,這個方法參考了SwipeRefreshLayout中的判定,是非常權威滴 requestDisallowInterceptTouchEvent()方法也是參考的SwipeRefreshLayout中的寫法,至於為什麼,參考SwipeRefreshLayout的事件處理。

最後一點就是onTouchEvent()的MotionEvent.ACTION_MOVE事件中的距離計算可能有點不好理解,這個就需要你對自定義View了解的比較深入才能明白了,如果說的比較透徹可能篇幅較大,各位童鞋單獨找資料學習吧 = =

最後一點,這種效果其實可以借助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, 0);
    }

    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(0).getMeasuredHeight();

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
                            int bottom) {
        View v1 = getChildAt(0);
        v1.layout(0, 0, v1.getMeasuredWidth(), v1.getMeasuredHeight());

        View v2 = getChildAt(1);
        v2.layout(0, 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 < 0) { // 表示按照實際的手指滑動距離向下移動的話太大,則直接計算出剛好頭部顯示出來的realDelt
                    scrollByDelt = 0 - getScrollY();
                } else if (realDelt > mHeadHeight) { // 同樣表示實際距離太大,計算出合適的距離
                    scrollByDelt = mHeadHeight - getScrollY();
                }
                scrollBy(0, (int) scrollByDelt);
                mDownPoint.set(event.getX(), y);
                break;
            case MotionEvent.ACTION_UP:
                mDownPoint.set(0, 0);
                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 < 21 && 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 < 14) {
            if (mTarget instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) mTarget;
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView
                        .getChildAt(0).getTop() < absListView
                        .getPaddingTop());
            } else {
                return ViewCompat.canScrollVertically(mTarget, -1)
                        || mTarget.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(mTarget, -1);
        }
    }

    /**
     * 檢查是否需要關閉或者打開
     */
    private void checkPosition() {
        // 移出去的大小,不能直接在if裡面用,否則返回值不正確
        int opOffset = getScrollY();
        if (opOffset < (mHeadHeight / 2)) {
            open();
        } else {
            close();
        }
    }

    /**
     * 向上移動,隱藏頭部
     */
    private void close() {
        mCollapsed = true;
        mScroller.startScroll(0, getScrollY(), 0, mHeadHeight - getScrollY());
        invalidate();
    }

    /**
     * 向下移動,頭部出現
     */
    private void open() {
        mCollapsed = false;
        mScroller.startScroll(0, getScrollY(), 0, -getScrollY());
        invalidate();
    }

    @Override
    public void computeScroll() {
        // 返回值為boolean,true說明滾動尚未完成,false說明滾動已經完成。
        if (mScroller.computeScrollOffset()) {
            // 移動位置
            scrollTo(0, mScroller.getCurrY());
            invalidate();
        }
    }

    /**
     * 可滾動view
     */
    private View mTarget;

    /**
     * 設置可滾動view
     */
    public void setTargetView(View v) {
        mTarget = v;
    }

}

以上,應該沒什麼大的bug,有bug大家留言 -_-

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved