Android-ObservableScrollView(一)

Android-ObservableScrollView Android-ObservableScrollView 用于观察可滚动视图上的滚动事件的 Android 库。 与 Android 5.0 Lollipop 中引入的 Toolbar 进行交互很容易,并且可能有助于实现 Material Design 应用程序的外观和感觉。 安装 dependencies { compile ' com.github.ksoichiro:android-observablescrollview:1.2.0 ' } 样品 请参阅observablescrollview-samples目录。 安装: $ ./gradlew installDebug 支持的小部件 小工具 自从 笔记 列表显示 v1.0.0 滚动视图 v1.0.0 网页视图 v1.0.0 回收器视图 v1.1.0 它受支持,但 RecyclerView 使用On 立即下载

项目地址:https://github.com/ksoichiro/Android-ObservableScrollView
项目依赖:com.github.ksoichiro:android-observablescrollview:1.6.0(版本号跟随官方版本变化)
效果图(下图来自上面github)


之前已经把这篇写好了,一不小心在编辑其他blog是把本篇搞掉了,于是乎苦逼的重写开始了,啥也不说了直接贴源码,带中文注释。

Scrollable


/**
 * 该接口为滑动控件提供相应的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);
}

ScrollState

/**
 * 滑动组件的滑动状态
 */
public enum ScrollState {
    /**
     * 组件停止滑动
     * 这个状态并不代表滑动控件从为滑动
     */
    STOP,

    /**
     *上滑动
     */
    UP,

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

ScrollUtils


/**
 * 滑动效果辅助类
 */
public final class ScrollUtils {
    /**
     * 私有构造防止new实例
     */
    private ScrollUtils() {
    }

    /**
     * 返回一个有效值,该值的范围在【minValue,MaxValue】
     */
    public static float getFloat(final float value, final float minValue, final float maxValue) {
        return Math.min(maxValue, Math.max(minValue, value));
    }

    /**
     * 创建一个带透明度的color的颜色值,透明度范围0-255
     * 背景色改变用到它
     * @param alpha    透明度
     * @param baseColor 传入颜色值
     */
    public static int getColorWithAlpha(float alpha, int baseColor) {
        int a = Math.min(255, Math.max(0, (int) (alpha * 255))) << 24;
        int rgb = 0x00ffffff & baseColor;
        return a + rgb;
    }

    /**
     * 为视图添加监听,不需要的时候需要移除监听器
     * @param view     监听的目标视图
     * @param runnable Runnable to be executed after the view is laid out.
     */
    public static void addOnGlobalLayoutListener(final View view, final Runnable runnable) {
        ViewTreeObserver vto = view.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @SuppressWarnings("deprecation")
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                    view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                } else {
                    view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
                runnable.run();
            }
        });
    }

    /**
     * 混合两种颜色。并指定透明度
     * @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) {
        float[] fromCmyk = cmykFromRgb(fromColor);
        float[] toCmyk = cmykFromRgb(toColor);
        float[] result = new float[4];
        for (int i = 0; i < 4; i++) {
            result[i] = Math.min(1, fromCmyk[i] * (1 - toAlpha) + toCmyk[i] * toAlpha);
        }
        return 0xff000000 + (0x00ffffff & ScrollUtils.rgbFromCmyk(result));
    }

    /**
     * RGB颜色转换为CMYK颜色。
     *
     * @param rgbColor Target color.
     * @return CMYK array.
     */
    public static float[] cmykFromRgb(int rgbColor) {
        int red = (0xff0000 & rgbColor) >> 16;
        int green = (0xff00 & rgbColor) >> 8;
        int blue = (0xff & rgbColor);
        float black = Math.min(1.0f - red / 255.0f, Math.min(1.0f - green / 255.0f, 1.0f - blue / 255.0f));
        float cyan = 1.0f;
        float magenta = 1.0f;
        float yellow = 1.0f;
        if (black != 1.0f) {
            // black 1.0 causes zero divide
            cyan = (1.0f - (red / 255.0f) - black) / (1.0f - black);
            magenta = (1.0f - (green / 255.0f) - black) / (1.0f - black);
            yellow = (1.0f - (blue / 255.0f) - black) / (1.0f - black);
        }
        return new float[]{cyan, magenta, yellow, black};
    }

    /**
     * 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) {
        float cyan = cmyk[0];
        float magenta = cmyk[1];
        float yellow = cmyk[2];
        float black = cmyk[3];
        int red = (int) ((1.0f - Math.min(1.0f, cyan * (1.0f - black) + black)) * 255);
        int green = (int) ((1.0f - Math.min(1.0f, magenta * (1.0f - black) + black)) * 255);
        int blue = (int) ((1.0f - Math.min(1.0f, yellow * (1.0f - black) + black)) * 255);
        return ((0xff & red) << 16) + ((0xff & green) << 8) + (0xff & blue);
    }
}

TouchInterceptionFrameLayout


/**
 * 一个布局接触滑动事件的拦截,布局提供移动滚动视图的容器使用滚动的位置。
 * 请注意,这个类覆盖或使用触摸事件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;

    private MotionEvent mPendingDownMotionEvent;

    private TouchInterceptionListener mTouchInterceptionListener;

    public TouchInterceptionFrameLayout(Context context) {
        super(context);
    }

    public TouchInterceptionFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TouchInterceptionFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public TouchInterceptionFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public void setScrollInterceptionListener(TouchInterceptionListener listener) {
        mTouchInterceptionListener = 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()) {
                case MotionEvent.ACTION_DOWN:
                    if (mIntercepting) {
                        mTouchInterceptionListener.onDownMotionEvent(ev);
                        duplicateTouchEventForChildren(ev);
                        return true;
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    // ACTION_MOVE will be passed suddenly, so initialize to avoid exception.
                    if (mInitialPoint == null) {
                        mInitialPoint = new PointF(ev.getX(), ev.getY());
                    }

                    // diffX and diffY are the origin of the motion, and should be difference
                    // from the position of the ACTION_DOWN event occurred.
                    float diffX = ev.getX() - mInitialPoint.x;
                    float diffY = ev.getY() - mInitialPoint.y;
                    mIntercepting = mTouchInterceptionListener.shouldInterceptTouchEvent(ev, true, diffX, diffY);
                    if (mIntercepting) {
                        // If this layout didn't receive ACTION_DOWN motion event,
                        // we should generate ACTION_DOWN event with current position.
                        if (!mBeganFromDownMotionEvent) {
                            mBeganFromDownMotionEvent = true;

                            MotionEvent event = MotionEvent.obtainNoHistory(mPendingDownMotionEvent);
                            event.setLocation(ev.getX(), ev.getY());
                            mTouchInterceptionListener.onDownMotionEvent(event);

                            mInitialPoint = new PointF(ev.getX(), ev.getY());
                            diffX = diffY = 0;
                        }

                        // Children's touches should be canceled
                        if (!mChildrenEventsCanceled) {
                            mChildrenEventsCanceled = true;
                            duplicateTouchEventForChildren(obtainMotionEvent(ev, MotionEvent.ACTION_CANCEL));
                        }

                        mTouchInterceptionListener.onMoveMotionEvent(ev, diffX, diffY);

                        // If next mIntercepting become false,
                        // then we should generate fake ACTION_DOWN event.
                        // Therefore we set pending flag to true as if this is a down motion event.
                        mDownMotionEventPended = true;

                        // Whether or not this event is consumed by the listener,
                        // assume it consumed because we declared to intercept the event.
                        return true;
                    } else {
                        if (mDownMotionEventPended) {
                            mDownMotionEventPended = false;
                            MotionEvent event = MotionEvent.obtainNoHistory(mPendingDownMotionEvent);
                            event.setLocation(ev.getX(), ev.getY());
                            duplicateTouchEventForChildren(ev, event);
                        } else {
                            duplicateTouchEventForChildren(ev);
                        }

                        // If next mIntercepting become true,
                        // then we should generate fake ACTION_DOWN event.
                        // Therefore we set beganFromDownMotionEvent flag to false
                        // as if we haven't received a down motion event.
                        mBeganFromDownMotionEvent = false;

                        // Reserve children's click cancellation here if they've already canceled
                        mChildrenEventsCanceled = false;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    mBeganFromDownMotionEvent = false;
                    if (mIntercepting) {
                        mTouchInterceptionListener.onUpOrCancelMotionEvent(ev);
                    }

                    // Children's touches should be canceled regardless of
                    // whether or not this layout intercepted the consecutive motion events.
                    if (!mChildrenEventsCanceled) {
                        mChildrenEventsCanceled = true;
                        if (mDownMotionEventPended) {
                            mDownMotionEventPended = false;
                            MotionEvent event = MotionEvent.obtainNoHistory(mPendingDownMotionEvent);
                            event.setLocation(ev.getX(), ev.getY());
                            duplicateTouchEventForChildren(ev, event);
                        } else {
                            duplicateTouchEventForChildren(ev);
                        }
                    }
                    return true;
            }
        }
        return super.onTouchEvent(ev);
    }

    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) {
        if (ev == null) {
            return;
        }
        for (int i = getChildCount() - 1; 0 <= i; i--) {
            View childView = getChildAt(i);
            if (childView != null) {
                Rect childRect = new Rect();
                childView.getHitRect(childRect);
                MotionEvent event = MotionEvent.obtainNoHistory(ev);
                if (!childRect.contains((int) event.getX(), (int) event.getY())) {
                    continue;
                }
                float offsetX = -childView.getLeft();
                float offsetY = -childView.getTop();
                boolean consumed = false;
                if (pendingEvents != null) {
                    for (MotionEvent pe : pendingEvents) {
                        if (pe != null) {
                            MotionEvent peAdjusted = MotionEvent.obtainNoHistory(pe);
                            peAdjusted.offsetLocation(offsetX, offsetY);
                            consumed |= childView.dispatchTouchEvent(peAdjusted);
                        }
                    }
                }
                event.offsetLocation(offsetX, offsetY);
                consumed |= childView.dispatchTouchEvent(event);
                if (consumed) {
                    break;
                }
            }
        }
    }
}

挑一个源码实现简单过一遍ObervableScrollView(源码太多建议忽略这些)

/**
 * ScrollView that its scroll position can be observed.
 */
public class ObservableScrollView extends ScrollView implements Scrollable {

    // Fields that should be saved onSaveInstanceState
    private int mPrevScrollY;
    private int mScrollY;

    // 我不得不说这个代码有点Android源码的味道,如果你看过ViewPager相关源码你就发现
    private ObservableScrollViewCallbacks mCallbacks;
    private List<ObservableScrollViewCallbacks> mCallbackCollection;

    private ScrollState mScrollState;
    private boolean mFirstScroll;
    private boolean mDragging;
    private boolean mIntercepted;
    private MotionEvent mPrevMoveEvent;
    private ViewGroup mTouchInterceptionViewGroup;


    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (hasNoCallbacks()) {
            return;
        }
        mScrollY = t;

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

        if (mPrevScrollY < t) {//计算滑动距离设置滑动方向
            mScrollState = ScrollState.UP;
        } else if (t < mPrevScrollY) {
            mScrollState = ScrollState.DOWN;

        }
        mPrevScrollY = t;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (hasNoCallbacks()) {
            return super.onInterceptTouchEvent(ev);
        }
        switch (ev.getActionMasked()) {//调用native层方法返回Action具体类型
            case MotionEvent.ACTION_DOWN:

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

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (hasNoCallbacks()) {
            return super.onTouchEvent(ev);
        }
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mIntercepted = false;
                mDragging = false;
                dispatchOnUpOrCancelMotionEvent(mScrollState);
                break;
            case MotionEvent.ACTION_MOVE:
                if (mPrevMoveEvent == null) {
                    mPrevMoveEvent = ev;
                }
                float diffY = ev.getY() - mPrevMoveEvent.getY();
                mPrevMoveEvent = MotionEvent.obtainNoHistory(ev);
                if (getCurrentScrollY() - diffY <= 0) {
                    // 不能滚动了。

                    if (mIntercepted) {
                        //ViewGroup已经派出ACTION_DOWN事件,所以停止在这里。
                        return false;
                    }

                    // Apps can set the interception target other than the direct parent.
                    final ViewGroup parent;
                    if (mTouchInterceptionViewGroup == null) {
                        parent = (ViewGroup) getParent();
                    } else {
                        parent = mTouchInterceptionViewGroup;
                    }

                    // Get offset to parents. If the parent is not the direct parent,
                    // we should aggregate offsets from all of the parents.
                    float offsetX = 0;
                    float offsetY = 0;
                    for (View v = this; v != null && v != parent; v = (View) v.getParent()) {
                        offsetX += v.getLeft() - v.getScrollX();
                        offsetY += v.getTop() - v.getScrollY();
                    }
                    final MotionEvent event = MotionEvent.obtainNoHistory(ev);
                    event.offsetLocation(offsetX, offsetY);

                    if (parent.onInterceptTouchEvent(event)) {
                        mIntercepted = true;

                        // If the parent wants to intercept ACTION_MOVE events,
                        // we pass ACTION_DOWN event to the parent
                        // as if these touch events just have began now.
                        event.setAction(MotionEvent.ACTION_DOWN);

                        // Return this onTouchEvent() first and set ACTION_DOWN event for parent
                        // to the queue, to keep events sequence.
                        post(new Runnable() {
                            @Override
                            public void run() {
                                parent.dispatchTouchEvent(event);
                            }
                        });
                        return false;
                    }
                    // Even when this can't be scrolled anymore,
                    // simply returning false here may cause subView's click,
                    // so delegate it to super.
                    return super.onTouchEvent(ev);
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

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

    /**
     * 添加一个监听器,使用方法类似ViewPager addPageChangeListener
     **/
    @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();
        }
    }

    @Override
    public void setTouchInterceptionViewGroup(ViewGroup viewGroup) {
        mTouchInterceptionViewGroup = viewGroup;
    }

    /**
     * 垂直滑动到指定Y位置
     */
    @Override
    public void scrollVerticallyTo(int y) {
        scrollTo(0, y);
    }

    @Override
    public int getCurrentScrollY() {
        return mScrollY;
    }

    /**
     * Action_Down时候回调
     */
    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();
            }
        }
    }

    /**
     * 滑动变化回调
     **/
    private void dispatchOnScrollChanged(int scrollY, boolean firstScroll, boolean dragging) {
        if (mCallbacks != null) {
            mCallbacks.onScrollChanged(scrollY, firstScroll, dragging);
        }
        if (mCallbackCollection != null) {
            for (int i = 0; i < mCallbackCollection.size(); i++) {
                ObservableScrollViewCallbacks callbacks = mCallbackCollection.get(i);
                callbacks.onScrollChanged(scrollY, firstScroll, dragging);
            }
        }
    }

    /**
     * ACTION_UP OR ACTION_CANCEL会执行相应的回调
     **/
    private void dispatchOnUpOrCancelMotionEvent(ScrollState scrollState) {
        if (mCallbacks != null) {
            mCallbacks.onUpOrCancelMotionEvent(scrollState);
        }
        if (mCallbackCollection != null) {
            for (int i = 0; i < mCallbackCollection.size(); i++) {
                ObservableScrollViewCallbacks callbacks = mCallbackCollection.get(i);
                callbacks.onUpOrCancelMotionEvent(scrollState);
            }
        }
    }

    /**
     * 是否有回调监听器
     * @return
     */
    private boolean hasNoCallbacks() {
        return mCallbacks == null && mCallbackCollection == null;
    }

    //.........略...............
}

其他自定义控件和这个ObservableScrollView都差不多,下面具体实战效果图(结合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的show和hide方法,关于show hide方法的具体实现在ActionBarImpl里面下面是具体实现代码(http://code1.okbase.net/codefile/ActionBarImpl.java_2014110527698_13.htm):

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

上面实战CityList的效果源码地址:http://download.youkuaiyun.com/detail/analyzesystem/9560909

SGP4/SDP4详解 **SGP4** (Simplified General Perturbations Version 4) 和 **SDP4** (Simplified Deep-space Perturbations Version 4) 是一组**解析轨道预报模型**,用于预测地球轨道卫星的位置和速度。它们是北美防空司令部(NORAD)维护的**TLE**数据格式的标准配套算法。 简单来说:**TLE是卫星轨道的"身份证",而SGP4/SDP4是解读这张身份证的"说明书"**。 阅读详情

相关推荐

手把手教你用Python处理MAHNOB-HCI多模态数据集(含脑电EEG/眼动/视频数据同步实战)

本文详细介绍了如何使用Python处理MAHNOB-HCI多模态数据集,涵盖脑电(EEG)、眼动追踪和视频数据的同步解析与对齐。通过MNE-Python、pandas和OpenCV等工具,实现从BDF、TSV到XML格式的高效处理,解决时间轴对齐难题,助力情感计算与人机交互研究。

weixin_30998797的博客 367

ObservableScrollView实现

自动隐藏布局 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"

琦琦的博客 6085

别再被网站识别成机器人了!用Python的undetected_chromedriver+Selenium实现无痕自动化

本文详细介绍了如何利用Python的undetected_chromedriver和Selenium实现无痕自动化,突破现代网站的反爬系统。通过修正浏览器指纹、模拟人类操作行为,并结合实战案例,帮助开发者构建难以被识别的自动化脚本,适用于电商监控等场景。

weixin_30367169的博客 251

CANN/ops-nn批量矩阵乘法NZ权重算子

| 产品 | 是否支持 | | :----------------------------------------------------------- | :------: | | <term>Ascend 950PR/Ascend 950DT</term>

gitblog_00019的博客 514

DASH 流媒体协议(Dynamic Adaptive Streaming over HTTP)概述——起源、MPD文件解析、相关项目

简单介绍DASH的起源与发展、MPD文件简要解析、相关项目,以及常见流媒体协议的比较

farewellyi的博客 6884

ESP32驱动TFT屏幕实战:LVGL与TFT_eSPI完美结合

本文详细介绍了如何在ESP32上结合TFT_eSPI驱动库与LVGL图形库,实现高效、流畅的嵌入式GUI开发。内容涵盖硬件连接、软件环境搭建、库配置、LVGL移植、界面创建及性能优化等全流程实战指南,帮助开发者快速构建低成本、高性能的交互式显示应用。

weixin_29206669的博客 200

【亲测免费】 探索 `ObservableScrollView`:让滚动更智能、更可控

探索 ObservableScrollView:让滚动更智能、更可控 本文将带你深入了解一款名为 ObservableScrollView 的开源项目,它提供了一种在 Android 开发中监听和控制 ScrollView 滚动的新方式。该项目由开发者 Lichenwei-Dev 维护,旨在为 Android 应用程序带来更加灵活的滚动交互体验。 项目简介 是一个 Android 的 View 包...

gitblog_00081的博客 505

VS2103+Intel2013+MSMPI运行Fortran并行程序

VS2103+Intel2013+MSMPI运行Fortran并行程序MSMPI安装新建Fortran90项目编写Fortran并行程序在cmd运行编译完成的执行文件 MSMPI安装 我这里采用的是Microsoft MPI v9.0.1,可以点此下载。下载完成后同时安装msmpisdk.msi、msmpisetup.exe两个文件,安装位置默认即可。 其中msmpisdk.msi的默认安装路径在C:\Program Files (x86)\Microsoft SDKs\MPI,该文件包含了mpi环境所需

weixin_43176703的博客 2936

Android-ObservableScrollView实现ActionBar动态显示隐藏的技术解析

Android-ObservableScrollView实现ActionBar动态显示隐藏的技术解析 前言 在移动应用开发中,优化屏幕空间利用是提升用户体验的重要环节。Android-ObservableScrollView作为一个强大的滚动视图监听库,为开发者提供了精细控制界面元素的能力。本文将深入解析如何利用该库实现ActionBar的动态显示与隐藏效果,这种交互模式常见于阅读类应用,能够在用...

gitblog_00234的博客 514

ADS学习笔记 2. 低噪声放大器设计

ADS学习笔记 2. 低噪声放大器设计。设计了一个中心频率433MHz,增益18.206dB,NF=0.134dB,S11带宽较好的低噪声放大器

u014087103的博客 9507

C# .NET6 TesseractOCR 图片文字识别

在你的项目中的tessdata-main文件夹找到这几个文件,并右键->属性设置为【如果较新则复制】有几个需要用到的模型,需要生成的时候复制到输出目录中,在项目中设置即可。TesseractOCR兼容 x86 与 x64。附 :ImageUtils.RegionCropping()源码。Visual Studio 2022 社区版。下载完成后解压在你的项目中,如图所示。从官方项目中获取模型数据, 网址为。

qq_44121554的博客 3852

ObservableScrollView滑动头部颜色从透明渐变成白色

ObservableScrollView,控制滑动距离来改变头部导航栏渐变颜色,Android上观察滚动的视图,可以监听滚动视图的滚动状态。它可以很简单地与Toolbar进行交互,并且很方便地创建Material Design的app,以下是我项目中所用到的,在此做个笔记,有需要的同学可以参考一下。 效果图如下: 1  xml文件中进行声明 <......Observable

scottma211的博客 1141

【数据集】中国陆地生态系统通量观测网 ChinaFLUX

【数据集】中国陆地生态系统通量观测网 ChinaFLUX

WW、forever的博客 187

Android-ObservableScrollView代码混淆配置指南

在Android应用开发中,代码混淆(ProGuard)是保护应用代码、减小APK体积的重要手段。本文将详细介绍如何为Android-ObservableScrollView库配置代码混淆规则,确保库功能正常运行的同时提供有效的代码保护。 ## 混淆配置基础 Android-ObservableScrollView作为一个提供滚动事件监听功能的库,其核心类和接口需要在混淆过程中得到保留。项目中...

gitblog_00165的博客 1007

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Android-ObservableScrollView 实战:填充 Toolbar 上方空隙(Filling Gap)的三种实现模式

本篇文章基于 Android-ObservableScrollView 官方基础教程中的「Filling gap on top of the Toolbar」一章([docs/basic/filling-gap.md](https://link.gitcode.com/i/1d2b1756a1b9be69e423bff4f9229754)),系统讲解在滚动视图中如何让 Header 区域自动"填满

gitblog_00939的博客 435

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Android-ObservableScrollView技术博客写作指南

Android-ObservableScrollView是一个用于监听滚动视图(Scrollable View)滚动事件的Android库,通过它可以轻松实现各种滚动交互效果,如ActionBar的显示/隐藏、Toolbar的动态变化等。该项目支持Android API 9及以上版本,提供了多种可观测滚动事件的视图组件,帮助开发者构建更具视觉吸引力的应用界面。 ### 核心功能 该库的核心价值...

gitblog_00944的博客 651

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

上一篇: Rx系列初探RxJava(一)
下一篇: Android-ObservableScrollView(二)
AnalyzeSystem
博客等级 码龄11年 204粉丝 114原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值