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

Android-ObservableScrollView(一)

編輯:關於Android編程

開源項目:Android-ObservableScrollView**
項目依賴添加:compile ‘com.github.ksoichiro:android-observablescrollview:1.6.0’(版本號自己參考下面地址提供)
開源地址:https://github.com/ksoichiro/Android-ObservableScrollView

效果圖(下圖來自上面鏈接):

\


以上效果雖然官方已有了具體實現,個人而言還是比較喜歡用開源的,最近有了個習慣,開源項目如果要用,那麼最少要把源碼看一遍,雖說不能完全看懂,但是至少要達到基於源碼庫定制開發沒問題。廢話不多說,下面看看ObservableScrollView結構

 

粗略一看貌似各個自定義控件都實現了Scrollable接口,就先從它相關類開始吧。

枚舉類型ScrollState定義滑動了方向


/**
 * 滑動組件的滑動狀態
 */
public enum ScrollState {
    /**
     * 組件停止滑動
     * 這個狀態並不代表滑動控件從為滑動
     */
    STOP,

    /**
     *上滑動
     */
    UP,

    /**
     * 向下滑動
     */
    DOWN,
}

滑動輔助工具類ScrollUtils(以下的代碼塊方法具體實現自行參考源碼這裡避免篇幅太長,全部去掉),表示工具類的CMYK與ARGB轉換有點懵,為什麼要小於1呢,如有知道還請不吝賜教


/**
 * 滑動效果輔助類
 */
public final class ScrollUtils {
    /**
     * 私有構造防止new實例
     */
    private ScrollUtils() {
    }

    /**
     * 返回一個有效值,該值的范圍在【minValue,MaxValue】
     */
    public static float getFloat(final float value, final float minValue, final float maxValue) {

    }

    /**
     * 創建一個帶透明度的color的顏色值,透明度范圍0-255
     * 背景色改變用到它
     * @param alpha    透明度
     * @param baseColor 傳入顏色值
     */
    public static int getColorWithAlpha(float alpha, int baseColor) {

    }

    /**
     * 為視圖添加監聽,不需要的時候需要移除監聽器
     * @param view     監聽的目標視圖
     * @param runnable Runnable to be executed after the view is laid out.
     */
    public static void addOnGlobalLayoutListener(final View view, final Runnable runnable) {

    }

    /**
     * 混合兩種顏色。並指定透明度
     * @param fromColor
     * @param toColor   .
     * @param toAlpha
     * @return Mixed color value in ARGB. Alpha is fixed value (255).
     */
    public static int mixColors(int fromColor, int toColor, float toAlpha) {

    }

    /**
     * RGB顏色轉換為CMYK顏色。
     *
     * @param rgbColor Target color.
     * @return CMYK array.
     */
    public static float[] cmykFromRgb(int rgbColor) {

    }

    /**
     * CYMK顏色轉換為RGB顏色。
     * 這個方法不會檢查是否非空cmyk或有4個元素的數組。
     *
     * @param cmyk 目標CYMK顏色。每個值應該在0.0到1.0 f之間, 應設置在這個秩序:青色,品紅色,黃色,黑色。
     * @return ARGB color. Alpha is fixed value (255).
     */
    public static int rgbFromCmyk(float[] cmyk) {

    }
}

Touch事件是否攔截相關容器和監聽器的定義如下


/**
 * 一個布局接觸滑動事件的攔截,布局提供移動滾動視圖的容器使用滾動的位置。
 * 請注意,這個類覆蓋或使用觸摸事件API,如onTouchEvent onInterceptTouchEvent dispatchTouchEvent
 * 所以要小心當你處理觸摸這個事件。
 */
public class TouchInterceptionFrameLayout extends FrameLayout {

    /**
     * 觸摸事件回調接口
     */
    public interface TouchInterceptionListener {
        /**
         * 判斷是否要攔截當前視圖的觸摸事件
         *
         * @param ev     Motion event.
         * @param moving 是否在Action_Move移動中
         * @param diffX  如果實在移動(ACTION_MOVE)返回滑動的距離X
         * @param diffY  如果實在移動(ACTION_MOVE)返回滑動的距離Y
         * @return 返回是否攔截布局
         */
        boolean shouldInterceptTouchEvent(MotionEvent ev, boolean moving, float diffX, float diffY);

        /**
         * 攔截ACTION_DOWN事件
         *
         * @param ev Motion event.
         */
        void onDownMotionEvent(MotionEvent ev);

        /**
         * 攔截ACTION_MOVE事件並且攜帶參數滑動距離
         *
         * @param ev    Motion event.
         * @param diffX Difference between previous X and current X.
         * @param diffY Difference between previous Y and current Y.
         */
        void onMoveMotionEvent(MotionEvent ev, float diffX, float diffY);

        /**
         * 攔截ACTION_UP或者ACTION_CANCEL事件
         *
         * @param ev Motion event.
         */
        void onUpOrCancelMotionEvent(MotionEvent ev);
    }

    /**
     * 是否攔截
     */
    private boolean mIntercepting;

    /**
     * 是否攔截ACTION_DOWN
     */
    private boolean mDownMotionEventPended;

    /**
     * 剛進入ACTION_DOWN是否直接攔截
     */
    private boolean mBeganFromDownMotionEvent;

    /**
     * 子view取消touch 事件
     */
    private boolean mChildrenEventsCanceled;

    /**
     * 記錄初始數據得Point
     */
    private PointF mInitialPoint;

    //...............................

    public void setScrollInterceptionListener(TouchInterceptionListener listener) {

    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mTouchInterceptionListener == null) {
            //如果攔截Listener都不存在直接不攔截
            return false;
        }

        // 在這裡,我們必須接觸狀態變量進行初始化
        // 問我們是否應該攔截這個事件。
        // 我們是否應該攔截保存後的事件處理。

        //   private static native int nativeGetAction(long nativePtr); native方法獲取Action對應的int值
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                mInitialPoint = new PointF(ev.getX(), ev.getY());
                // private static native long nativeCopy(long destNativePtr, long sourceNativePtr,boolean keepHistory); 該方法是 MotionEvent.obtainNoHistory(ev);調用最終通過這個nativeCopy方法復制一個event,但是不完全復制
                mPendingDownMotionEvent = MotionEvent.obtainNoHistory(ev);
                mDownMotionEventPended = true;
                mIntercepting = mTouchInterceptionListener.shouldInterceptTouchEvent(ev, false, 0, 0);
                mBeganFromDownMotionEvent = mIntercepting;
                mChildrenEventsCanceled = false;
                return mIntercepting;
            case MotionEvent.ACTION_MOVE:
                // 避免mInitialPoint沒有被初始化拋出異常
                if (mInitialPoint == null) {
                    mInitialPoint = new PointF(ev.getX(), ev.getY());
                }

               //計算出滑動的距離以便於接口回調所需
                float diffX = ev.getX() - mInitialPoint.x;
                float diffY = ev.getY() - mInitialPoint.y;
                mIntercepting = mTouchInterceptionListener.shouldInterceptTouchEvent(ev, true, diffX, diffY);
                return mIntercepting;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mTouchInterceptionListener != null) {
            switch (ev.getActionMasked()) {//如果touch事件攔截監聽器存在,並且需要攔截,會執行相應的攔截回調以及根據條件選擇自定義的事件分發
                case MotionEvent.ACTION_DOWN:
                    if (mIntercepting) {
                        mTouchInterceptionListener.onDownMotionEvent(ev);
                        duplicateTouchEventForChildren(ev);
                        return true;
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                //...........................
    }

    private MotionEvent obtainMotionEvent(MotionEvent base, int action) {
        MotionEvent ev = MotionEvent.obtainNoHistory(base);
        ev.setAction(action);
        return ev;
    }

    /**
     * 重復的子視圖的觸摸事件。
     * 我們想分發一個滑動事件給子視圖,但是調用dispatchTouchEvent()會導致StackOverflowError。
     * 因此自己定義了一個事件分發。
     */
    private void duplicateTouchEventForChildren(MotionEvent ev, MotionEvent... pendingEvents) {
                //................
                consumed |= childView.dispatchTouchEvent(event);
                if (consumed) {
                    break;
                }
            }
        }
    }
}

Scrollable該接口為滑動控件提供相應的api


/**
 * 該接口為滑動控件提供相應的api
 */
public interface Scrollable {
    /**
     * 設置滑動的回調監聽這個方法啊已經過時了,已經有了更好的方法
     */
    @Deprecated
    void setScrollViewCallbacks(ObservableScrollViewCallbacks listener);

    /**
     * 添加一個滑動事件的回調監聽
     */
    void addScrollViewCallbacks(ObservableScrollViewCallbacks listener);

    /**
     * 移除滑動回調監聽器
     */
    void removeScrollViewCallbacks(ObservableScrollViewCallbacks listener);

    /**
     * 清楚所有滑動監聽器
     */
    void clearScrollViewCallbacks();

    /**
     * 垂直滾動到指定位置
     * @param y Vertical position to scroll to.
     */
    void scrollVerticallyTo(int y);

    /**
     *過去當前視圖位置所對應的Y值
     *
     * @return Current Y pixel.
     */
    int getCurrentScrollY();

    /**
     * 設置觸摸事件的父布局視圖ViewGroup
     *
     * @param viewGroup ViewGroup object to dispatch motion events.
     */
    void setTouchInterceptionViewGroup(ViewGroup viewGroup);
}

ObservableScrollViewCallbacks為滑動控件提供回調函數.


/**
 * 為滑動控件提供回調函數.
 */
public interface ObservableScrollViewCallbacks {
    /**
     * 如果發生滑動變化時會回調該函數,如果你初始化視圖時想回調該方法需要手動調用
     *
     * @param scrollY    在Y軸滾動位置。
     * @param firstScroll 是否是第一次(剛開始)滑動
     * @param dragging   當前視圖是否是因為拖拽而產生滑動
     */
    void onScrollChanged(int scrollY, boolean firstScroll, boolean dragging);

    /**
     * 向下滑動事件回調函數
     */
    void onDownMotionEvent();

    /**
     * 拖拽結束或者取消滑動
     *
     * @param scrollState 顯示滑動的方向
     */
    void onUpOrCancelMotionEvent(ScrollState scrollState);
}

該庫提供的自定義控件如下圖都實現了Scollable,內部具體實現都差不多,粗略看一遍就可以開始我們的實踐了。這裡就以ObserableScrollView為例粗略過一遍

   @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        //是否有監聽器(set 以及add的監聽器)
        if (hasNoCallbacks()) {
            return;
        }
        mScrollY = t;

        dispatchOnScrollChanged(t, mFirstScroll, mDragging);
        if (mFirstScroll) {
            mFirstScroll = false;
        }

        // 判斷滑動方向設置枚舉ScrollState
        if (mPrevScrollY < t) {
            mScrollState = ScrollState.UP;
        } else if (t < mPrevScrollY) {
            mScrollState = ScrollState.DOWN;
        }
        mPrevScrollY = t;
    }

上裡代碼塊關聯的回調以及相關參數參考TouchEvent相關方法的參數初始化,例如mDragging,回調監聽器參考了源碼的寫法,讓setListener的寫法過時,采用add的方式,具體看代碼塊(源碼中ViewPager有這樣的寫法,好處不言而喻)

public class ObservableScrollView extends ScrollView implements Scrollable {

    private ObservableScrollViewCallbacks mCallbacks;
    private List mCallbackCollection;

   @Override
    public void setScrollViewCallbacks(ObservableScrollViewCallbacks listener) {
        mCallbacks = listener;
    }

    @Override
    public void addScrollViewCallbacks(ObservableScrollViewCallbacks listener) {
        if (mCallbackCollection == null) {
            mCallbackCollection = new ArrayList<>();
        }
        mCallbackCollection.add(listener);
    }

    @Override
    public void removeScrollViewCallbacks(ObservableScrollViewCallbacks listener) {
        if (mCallbackCollection != null) {
            mCallbackCollection.remove(listener);
        }
    }

    @Override
    public void clearScrollViewCallbacks() {
        if (mCallbackCollection != null) {
            mCallbackCollection.clear();
        }
    }

ViewGroup事件判斷是否攔截以及子View的事件分發

   @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (hasNoCallbacks()) {
            return super.onInterceptTouchEvent(ev);
        }
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:

                mFirstScroll = mDragging = true;
                dispatchOnDownMotionEvent();
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    private void dispatchOnDownMotionEvent() {
        if (mCallbacks != null) {
            mCallbacks.onDownMotionEvent();
        }
        if (mCallbackCollection != null) {
            for (int i = 0; i < mCallbackCollection.size(); i++) {
                ObservableScrollViewCallbacks callbacks = mCallbackCollection.get(i);
                callbacks.onDownMotionEvent();
            }
        }
    }

onTouchEvent我也只了解個大概說不出什麼來,自己慢慢看吧。下面開始我們的實戰ActionBar+Observable系列控件,線上效果圖

\

以上效果根據我先有demo CityList修改而成(cityList庫分析地址http://blog.csdn.net/analyzesystem/article/details/46890905)上圖效果關於CitiyList部分就不細說了,核心代碼部分在於Activity實現ObservableScrollViewCallbacks,在回調方法onUpOrCancelMotionEvent方法做ActionBar的動態隱藏顯示。

  @Override
    public void onUpOrCancelMotionEvent(ScrollState scrollState) {
        ActionBar ab = getSupportActionBar();
        if (ab == null) {
            return;
        }
        if (scrollState == ScrollState.UP) {
            if (ab.isShowing()) {
                ab.hide();
            }
        } else if (scrollState == ScrollState.DOWN) {
            if (!ab.isShowing()) {
                ab.show();
            }
        }
    }

ActionBar的hide方法和show方法具體是怎麼實現的呢,根進源碼發現ActionBar只是定義了抽象方法,具體實現在那麼呢,在搜索資料過程中發現一篇關於ActionBar源碼分析的文章:http://blog.csdn.net/hello__zero/article/details/30517245?utm_source=tuicool&utm_medium=referral,根據文章提示進到ActionBarImpl源碼(http://code1.okbase.net/codefile/ActionBarImpl.java_2014110527698_13.htm)找到hide和show的具體實現

void show(boolean markHiddenBeforeMode) {
        if (mCurrentShowAnim != null) {
            mCurrentShowAnim.end();
        }
        if (mContainerView.getVisibility() == View.VISIBLE) {
            if (markHiddenBeforeMode) mWasHiddenBeforeMode = false;
            return;
        }
        mContainerView.setVisibility(View.VISIBLE);

        if (mShowHideAnimationEnabled) {
            mContainerView.setAlpha(0);
            AnimatorSet anim = new AnimatorSet();
            AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mContainerView, "alpha", 1));
            if (mContentView != null) {
                b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
                        -mContainerView.getHeight(), 0));
                mContainerView.setTranslationY(-mContainerView.getHeight());
                b.with(ObjectAnimator.ofFloat(mContainerView, "translationY", 0));
            }
            if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
                mSplitView.setAlpha(0);
                mSplitView.setVisibility(View.VISIBLE);
                b.with(ObjectAnimator.ofFloat(mSplitView, "alpha", 1));
            }
            anim.addListener(mShowListener);
            mCurrentShowAnim = anim;
            anim.start();
        } else {
            mContainerView.setAlpha(1);
            mContainerView.setTranslationY(0);
            mShowListener.onAnimationEnd(null);
        }
    }

    @Override
    public void hide() {
        if (mCurrentShowAnim != null) {
            mCurrentShowAnim.end();
        }
        if (mContainerView.getVisibility() == View.GONE) {
            return;
        }

        if (mShowHideAnimationEnabled) {
            mContainerView.setAlpha(1);
            mContainerView.setTransitioning(true);
            AnimatorSet anim = new AnimatorSet();
            AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mContainerView, "alpha", 0));
            if (mContentView != null) {
                b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
                        0, -mContainerView.getHeight()));
                b.with(ObjectAnimator.ofFloat(mContainerView, "translationY",
                        -mContainerView.getHeight()));
            }
            if (mSplitView != null && mSplitView.getVisibility() == View.VISIBLE) {
                mSplitView.setAlpha(1);
                b.with(ObjectAnimator.ofFloat(mSplitView, "alpha", 0));
            }
            anim.addListener(mHideListener);
            mCurrentShowAnim = anim;
            anim.start();
        } else {
            mHideListener.onAnimationEnd(null);
        }
    }

ObjectAnimator實現動畫效果的位移隱藏以及透明度的變化(如果你對上裡代表表示看不太懂,我想你應該補腦動畫相關用到的坐標系了),以上是通過ActionBar配合實現

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