Android中的沉浸式狀態欄效果

来源:http://www.cnblogs.com/huangjie123/archive/2016/12/16/6187904.html
-Advertisement-
Play Games

谷歌並沒有給出沉浸式狀態欄這個概念,谷歌只說了沉浸式模式(Immersive Mode)。 ...


       無意間瞭解到沉浸式狀態欄,感覺賊拉的高大上,於是就是試著去瞭解一下,就有了這篇文章。下麵就來瞭解一下啥叫沉浸式狀態欄。傳統的手機狀態欄是呈現出黑色條狀的,有的和手機主界面有很明顯的區別。這一樣就在一定程度上犧牲了視覺寬度,界面面積變小。Google從android kitkat(Android 4.4)開始,給我們開發者提供了一套能透明的系統ui樣式給狀態欄和導航欄,這樣的話就不用向以前那樣每天面對著黑乎乎的上下兩條黑欄了,還可以調成跟Activity一樣的樣式,形成一個完整的主題,和IOS7.0以上系統一樣了,沉浸式狀態欄和主界面顏色和諧一體,視覺效果更加炫酷。不過雖然聽上去好像是很高大上的沉浸式效果,實際看上去貌似就是將內容全屏化了而已嘛。其實這算是一個爭議點了。不少人糾結於沉浸式狀態欄到底是將屏幕顯示內容擴大還是僅僅是改變狀態欄、標題欄的顏色。其實我更傾向於後者。在4.4之前狀態欄一直是黑色的,在4.4中帶來了 windowTranslucentStatus 這一特性,因此可以實現給狀態欄設置顏色,視覺上的效果,感覺容器部分和狀態欄、標題欄融為一體,更加直接的說就是改變狀態欄、標題欄的顏色,當時可以根據界面顏色改變狀態欄、標題欄的顏色實現跟加完整的界面顯示,這應該是沉浸式狀態欄受追捧的原因吧。

     谷歌並沒有給出沉浸式狀態欄這個概念,谷歌只說了沉浸式模式(Immersive Mode)。不過沉浸式狀態欄這個名字其實挺不錯,只能隨大眾,但是Android的環境並沒有IOS環境一樣特別統一,比如華為rom的跟小米rom的虛擬按鍵完全不一樣,並且安卓版本眾多涉及到版本相容問題,所有Android開發者不容易。這點在沉浸式狀態欄的開發中顯得尤為重要。如果你在4.4之前的機子上顯示沉浸式狀態欄的話,經常出現一些意想不到的結果。沉浸式是APP界面圖片延伸到狀態欄, 應用本身沉浸於狀態欄,所以如果第三方的軟體沒有為狀態欄分配圖片,那麼自然就是黑色。頂端的狀態欄和下麵的虛擬按鍵都隱藏,需要的時候從邊緣划出。沉浸模式。當啟用該模式,應用程式的界面將占據整個屏幕,系統自動將隱藏系統的狀態欄和導航欄,讓應用程式內容可以在最大顯示範圍呈現,增加大屏體驗,而當需要查看通知的時候只需要從頂部向下滑動就能呼出通知欄。沉浸模式實際上有兩種: 一種叫“沉浸模式”,狀態欄和虛擬按鈕會自動隱藏、應用自動全屏,這種模式下,應用占據屏幕的全部空間, 只有當用戶從屏幕的上方邊沿處向下划動時, 才會退出沉浸模式, 用戶觸摸屏幕其它部分時, 不會退出該模式, 這種模式比較適用於閱讀器、 雜誌類應用。另外一種叫“黏性沉浸模式”,讓狀態欄和虛擬按鈕半透明,應用使用屏幕的全部空間, 當用戶從屏幕的上方邊沿處向下滑動時,也不會退出該模式, 但是系統界面 (狀態欄、 導航欄) 將會以半透明的效果浮現在應用視圖之上 , 只有當用戶點擊系統界面上的控制項時, 才會退出黏性沉浸模式。

     下麵來說一說具體的實現。一個Android應用程式的界面上其實是有很多系統元素的,有狀態欄、ActionBar、導航欄等。而打造沉浸式模式的用戶體驗,就是要將這些系統元素進行整合,當主界面改變時,狀態欄、ActionBar、導航欄同時也發生改變。這裡先調用getWindow().getDecorView()方法獲取到了當前界面的DecorView,然後調用它的setSystemUiVisibility()方法來設置系統UI元素的可見性。其中,SYSTEM_UI_FLAG_FULLSCREEN表示全屏的意思,也就是會將狀態欄隱藏。另外,根據Android的設計建議,ActionBar是不應該獨立於狀態欄而單獨顯示的,因此狀態欄如果隱藏了,我們同時也需要調用ActionBar的hide()方法將ActionBar也進行隱藏這種效果不叫沉浸式狀態欄,也完全沒有沉浸式狀態欄這種說法,我們估且可以把它叫做透明狀態欄效果吧。

      隱藏狀態欄:

setContentView(R.layout.activity_main);  //再該方法後執行
if (Build.VERSION.SDK_INT >= 21) {
    View decorView = getWindow().getDecorView();
    int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
    decorView.setSystemUiVisibility(option);
    getWindow().setStatusBarColor(Color.TRANSPARENT);
}
ActionBar actionBar = getSupportActionBar();
actionBar.hide();

 

 

     具體的沉浸效果該如何實現呢,系統提供實現沉浸式狀態欄的方法,通過WindowManager來實現,可分為兩步:
       1. 在需要實現沉浸式狀態欄的Activity的佈局中添加以下參數

            android:fitsSystemWindows="true"
            android:clipToPadding="true"

     2. 在Activity的setContentView()方法後面調用初始化的方法即可。
    

  private void initState() {
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            //透明狀態欄
             getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            //透明導航欄
             getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
           }
       }

       當上述的實現效果,其實並不好, 沒有在佈局中設置clipToPadding為true的時候,會對應用的頂部Toolbar進行拉伸,在佈局中兩個參數都進行設置後,頂部狀態欄變成了白色。這樣,我在github上找到一個很好的沉浸狀態欄效果,來看一下。

      首先添加依賴,導入下麵的包。有時候可能會出現版本不統一的問題,依次保證聯網的情況下點擊一下同步android studio會自動下載包。

compile 'com.jaeger.statusbaruitl:library:1.2.5'

 

      在自定義控制項中實現的進本邏輯,代碼較長。

package com.xiaoyuan;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.widget.ScrollView;

import java.util.ArrayList;

/**
 * @author Emil Sj�lander - [email protected]
 */
public class StickyScrollView extends ScrollView {

    /**
     * Tag for views that should stick and have constant drawing. e.g.
     * TextViews, ImageViews etc
     */
    public static final String STICKY_TAG = "sticky";

    /**
     * Flag for views that should stick and have non-constant drawing. e.g.
     * Buttons, ProgressBars etc
     */
    public static final String FLAG_NONCONSTANT = "-nonconstant";

    /**
     * Flag for views that have aren't fully opaque
     */
    public static final String FLAG_HASTRANSPARANCY = "-hastransparancy";

    /**
     * Default height of the shadow peeking out below the stuck view.
     */
    private static final int DEFAULT_SHADOW_HEIGHT = 10; // dp;
    /**
     * XKJ add for add 50dp offset of top
     */
    private static int MIN_STICK_TOP = 100;// px
    //    private static final int MIN_STICK_TOP = 0;
    private ArrayList<View> stickyViews;
    private View currentlyStickingView;
    private float stickyViewTopOffset;
    private int stickyViewLeftOffset;
    private boolean redirectTouchesToStickyView;
    private boolean clippingToPadding;
    private boolean clipToPaddingHasBeenSet;

    private int mShadowHeight;
    private Drawable mShadowDrawable;
    private OnScrollChangedListener mOnScrollHandler = null;
    private IOnScrollToEnd mOnScrollToEnd = null;

    private final Runnable invalidateRunnable = new Runnable() {

        @Override
        public void run() {
            if (currentlyStickingView != null) {
                int l = getLeftForViewRelativeOnlyChild(currentlyStickingView);
                int t = getBottomForViewRelativeOnlyChild(currentlyStickingView);
                int r = getRightForViewRelativeOnlyChild(currentlyStickingView);
                int b = (int) (getScrollY() + (currentlyStickingView.getHeight() + stickyViewTopOffset));
                invalidate(l, t, r, b);
            }
            postDelayed(this, 16);
        }
    };

    public StickyScrollView(Context context) {
        this(context, null);
    }

    public StickyScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.scrollViewStyle);
    }

    public StickyScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup();

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StickyScrollView, defStyle, 0);

        final float density = context.getResources().getDisplayMetrics().density;
        int defaultShadowHeightInPix = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);

        mShadowHeight = a.getDimensionPixelSize(R.styleable.StickyScrollView_stuckShadowHeight,
                defaultShadowHeightInPix);

        int shadowDrawableRes = a.getResourceId(R.styleable.StickyScrollView_stuckShadowDrawable, -1);

        if (shadowDrawableRes != -1) {
            mShadowDrawable = context.getResources().getDrawable(shadowDrawableRes);
        }

        a.recycle();

    }

    /**
     * Sets the height of the shadow drawable in pixels.
     *
     * @param height
     */
    public void setShadowHeight(int height) {
        mShadowHeight = height;
    }

    public void setup() {
        stickyViews = new ArrayList<View>();
    }

    private int getLeftForViewRelativeOnlyChild(View v) {
        int left = v.getLeft();
        while (v.getParent() != getChildAt(0)) {
            v = (View) v.getParent();
            left += v.getLeft();
        }
        return left;
    }

    private int getTopForViewRelativeOnlyChild(View v) {
        int top = v.getTop();
        while (v.getParent() != getChildAt(0)) {
            v = (View) v.getParent();
            top += v.getTop();
        }
        return top;
    }

    private int getRightForViewRelativeOnlyChild(View v) {
        int right = v.getRight();
        while (v.getParent() != getChildAt(0)) {
            v = (View) v.getParent();
            right += v.getRight();
        }
        return right;
    }

    private int getBottomForViewRelativeOnlyChild(View v) {
        int bottom = v.getBottom();
        while (v.getParent() != getChildAt(0)) {
            v = (View) v.getParent();
            bottom += v.getBottom();
        }
        return bottom;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (!clipToPaddingHasBeenSet) {
            clippingToPadding = true;
        }
        notifyHierarchyChanged();
    }

    @Override
    public void setClipToPadding(boolean clipToPadding) {
        super.setClipToPadding(clipToPadding);
        clippingToPadding = clipToPadding;
        clipToPaddingHasBeenSet = true;
    }

    @Override
    public void addView(View child) {
        super.addView(child);
        findStickyViews(child);
    }

    @Override
    public void addView(View child, int index) {
        super.addView(child, index);
        findStickyViews(child);
    }

    @Override
    public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
        super.addView(child, index, params);
        findStickyViews(child);
    }

    @Override
    public void addView(View child, int width, int height) {
        super.addView(child, width, height);
        findStickyViews(child);
    }

    @Override
    public void addView(View child, android.view.ViewGroup.LayoutParams params) {
        super.addView(child, params);
        findStickyViews(child);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (currentlyStickingView != null) {
            canvas.save();
            canvas.translate(getPaddingLeft() + stickyViewLeftOffset, getScrollY() + stickyViewTopOffset
                    + (clippingToPadding ? getPaddingTop() : 0));

            canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), getWidth() - stickyViewLeftOffset,
                    currentlyStickingView.getHeight() + mShadowHeight + 1);

            if (mShadowDrawable != null) {
                int left = 0;
                int right = currentlyStickingView.getWidth();
                int top = currentlyStickingView.getHeight();
                int bottom = currentlyStickingView.getHeight() + mShadowHeight;
                mShadowDrawable.setBounds(left, top, right, bottom);
                mShadowDrawable.draw(canvas);
            }

            canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), getWidth(),
                    currentlyStickingView.getHeight());
            if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) {
                showView(currentlyStickingView);
                currentlyStickingView.draw(canvas);
                hideView(currentlyStickingView);
            } else {
                currentlyStickingView.draw(canvas);
            }
            canvas.restore();
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            redirectTouchesToStickyView = true;
        }

        if (redirectTouchesToStickyView) {
            redirectTouchesToStickyView = currentlyStickingView != null;
            if (redirectTouchesToStickyView) {
                redirectTouchesToStickyView = ev.getY() <= (currentlyStickingView.getHeight() + stickyViewTopOffset)
                        && ev.getX() >= getLeftForViewRelativeOnlyChild(currentlyStickingView)
                        && ev.getX() <= getRightForViewRelativeOnlyChild(currentlyStickingView);
            }
        } else if (currentlyStickingView == null) {
            redirectTouchesToStickyView = false;
        }
        if (redirectTouchesToStickyView) {
            ev.offsetLocation(0, -1
                    * ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView)));

            // XKJ add TODO: remove this
            currentlyStickingView.invalidate();
        }
        return super.dispatchTouchEvent(ev);
    }

    private boolean hasNotDoneActionDown = true;

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (redirectTouchesToStickyView) {
            ev.offsetLocation(0,
                    ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView)));
        }

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            hasNotDoneActionDown = false;
        }

        if (hasNotDoneActionDown) {
            MotionEvent down = MotionEvent.obtain(ev);
            down.setAction(MotionEvent.ACTION_DOWN);
            super.onTouchEvent(down);
            hasNotDoneActionDown = false;
        }

        if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
            hasNotDoneActionDown = true;
        }

        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        doTheStickyThing();
        if (mOnScrollHandler != null) {
            mOnScrollHandler.onScrollChanged(l, t, oldl, oldt);
        }
        int maxScroll = getChildAt(0).getHeight() - getHeight();
        if (getChildCount() > 0 && t == maxScroll) {
            if (mOnScrollToEnd != null) {
                mOnScrollToEnd.onScrollToEnd();
            }
        }
    }

    public void setOnScrollListener(OnScrollChangedListener handler) {
        mOnScrollHandler = handler;
    }

    public interface OnScrollChangedListener {
        public void onScrollChanged(int l, int t, int oldl, int oldt);
    }

    public interface IOnScrollToEnd {
        public void onScrollToEnd();
    }

    public void setOnScrollToEndListener(IOnScrollToEnd handler) {
        mOnScrollToEnd = handler;
    }

    private void doTheStickyThing() {
        View viewThatShouldStick = null;
        View approachingView = null;
        for (View v : stickyViews) {
            int viewTop = getTopForViewRelativeOnlyChild(v) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop())
                    - MIN_STICK_TOP;// add 50dp

            if (viewTop <= 0) {
                if (viewThatShouldStick == null
                        || viewTop > (getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (clippingToPadding ? 0
                        : getPaddingTop()))) {
                    viewThatShouldStick = v;
                }
            } else {
                if (approachingView == null
                        || viewTop < (getTopForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0
                        : getPaddingTop()))) {
                    approachingView = v;
                }
            }
        }
        if (viewThatShouldStick != null) {
            stickyViewTopOffset = approachingView == null ? MIN_STICK_TOP : Math.min(MIN_STICK_TOP,
                    getTopForViewRelativeOnlyChild(approachingView) - getScrollY()
                            + (clippingToPadding ? 0 : getPaddingTop()) - viewThatShouldStick.getHeight());
            if (viewThatShouldStick != currentlyStickingView) {
                if (currentlyStickingView != null) {
                    stopStickingCurrentlyStickingView();
                }
                // only compute the left offset when we start sticking.
                stickyViewLeftOffset = getLeftForViewRelativeOnlyChild(viewThatShouldStick);
                startStickingView(viewThatShouldStick);
            }
        } else if (currentlyStickingView != null) {
            stopStickingCurrentlyStickingView();
        }
    }

    private void startStickingView(View viewThatShouldStick) {
        currentlyStickingView = viewThatShouldStick;
        if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) {
            hideView(currentlyStickingView);
        }
        if (((String) currentlyStickingView.getTag()).contains(FLAG_NONCONSTANT)) {
            post(invalidateRunnable);
        }
    }

    private void stopStickingCurrentlyStickingView() {
        if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) {
            showView(currentlyStickingView);
        }
        currentlyStickingView = null;
        removeCallbacks(invalidateRunnable);
    }

    /**
     * Notify that the sticky attribute has been added or removed from one or
     * more views in the View hierarchy
     */
    public void notifyStickyAttributeChanged() {
        notifyHierarchyChanged();
    }

    private void notifyHierarchyChanged() {
        if (currentlyStickingView != null) {
            stopStickingCurrentlyStickingView();
        }
        stickyViews.clear();
        findStickyViews(getChildAt(0));
        doTheStickyThing();
        invalidate();
    }

    private void findStickyViews(View v) {
        if (v instanceof ViewGroup) {
            ViewGroup vg = (ViewGroup) v;
            for (int i = 0; i < vg.getChildCount(); i++) {
                String tag = getStringTagForView(vg.getChildAt(i));
                if (tag != null && tag.contains(STICKY_TAG)) {
                    stickyViews.add(vg.getChildAt(i));
                } else if (vg.getChildAt(i) instanceof ViewGroup) {
                    findStickyViews(vg.getChildAt(i));
                }
            }
        } else {
            String tag = (String) v.getTag();
            if (tag != null && tag.contains(STICKY_TAG)) {
                stickyViews.add(v);
            }
        }
    }

    private String getStringTagForView(View v) {
        Object tagObject = v.getTag();
        return String.valueOf(tagObject);
    }

    private void hideView(View v) {
        if (Build.VERSION.SDK_INT >= 11) {
            v.setAlpha(0);
        } else {
            AlphaAnimation anim = new AlphaAnimation(1, 0);
            anim.setDuration(0);
            anim.setFillAfter(true);
            v.startAnimation(anim);
        }
    }

    private void showView(View v) {
        if (Build.VERSION.SDK_INT >= 11) {
            v.setAlpha(1);
        } else {
            AlphaAnimation anim = new AlphaAnimation(0, 1);
            anim.setDuration(0);
            anim.setFillAfter(true);
            v.startAnimation(anim);
        }
    }

    /**
     * 設置懸浮高度
     * @param height
     */
    public void setStickTop(int height) {
        MIN_STICK_TOP = height;
    }

    /**
     * 解決vviewpager在scrollview滑動衝突的問題
     */
    // 滑動距離及坐標
    private float xDistance, yDistance, xLast, yLast;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                xDistance = yDistance = 0f;
                xLast = ev.getX();
                yLast = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                final float curX = ev.getX();
                final float curY = ev.getY();

                xDistance += Math.abs(curX - xLast);
                yDistance += Math.abs(curY - yLast);
//                com.ihaveu.utils.Log.i("test", "curx:"+curX+",cury:"+curY+",xlast:"+xLast+",ylast:"+yLast);
//                xLast = curX;
//                yLast = curY;

                if (xDistance > yDistance) {
                    return false;
                }

        }
        return super.onInterceptTouchEvent(ev);
    }
}

        接下來是調用自定義控制項了,用到兩個關鍵的方法。StatusBarUtil.setTranslucentForImageView(MainActivity.this, 0, title)和llTitle.setBackgroundColor(Color.argb((int) alpha, 227, 29, 26))分別設置狀態欄和標題欄的顏色。       

package com.xiaoyuan;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.jaeger.library.StatusBarUtil;

public class MainActivity extends AppCompatActivity implements View.OnClickListener, StickyScrollView.OnScrollChangedListener {

    TextView oneTextView, twoTextView;
    private StickyScrollView stickyScrollView;
    private int height;
    private LinearLayout llContent;
    private RelativeLayout llTitle;
    private FrameLayout frameLayout;
    private TextView title;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initListeners();
    }

    /**
     * 初始化View
     */
    private void initView() {
        stickyScrollView = (StickyScrollView) findViewById(R.id.scrollView);
        frameLayout = (FrameLayout) findViewById(R.id.tabMainContainer);
        title = (TextView) findViewById(R.id.title);
        oneTextView = (TextView) findViewById(R.id.infoText);
        llContent = (LinearLayout) findViewById(R.id.ll_content);
        llTitle = (RelativeLayout) findViewById(R.id.ll_good_detail);
        oneTextView.setOnClickListener(this);
        twoTextView = (TextView) findViewById(R.id.secondText);
        twoTextView.setOnClickListener(this);

        stickyScrollView.setOnScrollListener(this);
        StatusBarUtil.setTranslucentForImageView(MainActivity.this, 0, title);
        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) llTitle.getLayoutParams();
        params.setMargins(0, getStatusHeight(), 0, 0);
        llTitle.setLayoutParams(params);

        //預設設置一個Frg
        getSupportFragmentManager().beginTransaction().replace(R.id.tabMainContainer, Fragment.newInstance()).commit();
    }

    /**
     * 獲取狀態欄高度
     *
     * @return
     */
    private int getStatusHeight() {
        int resourceId = MainActivity.this.getResources().getIdentifier("status_bar_height", "dimen", "android");
        return getResources().getDimensionPixelSize(resourceId);

    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.infoText) {
            getSupportFragmentManager().beginTransaction().replace(R.id.tabMainContainer, Fragment.newInstance()).commit();
        } else if (v.getId() == R.id.secondText) {
            getSupportFragmentManager().beginTransaction().replace(R.id.tabMainContainer, Fragment1.newInstance()).commit();

        }
    }


    private void initListeners() {
        //獲取內容總高度
        final ViewTreeObserver vto = llContent.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                height = llContent.getHeight();
                //註意要移除
                llContent.getViewTreeObserver()
                        .removeGlobalOnLayoutListener(this);

            }
        });

        //獲取Fragment高度
        ViewTreeObserver viewTreeObserver = frameLayout.getViewTreeObserver();
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                height = height - frameLayout.getHeight();
                //註意要移除
                frameLayout.getViewTreeObserver()
                        .removeGlobalOnLayoutListener(this);
            }
        });

        //獲取title高度
        ViewTreeObserver viewTreeObserver1 = llTitle.getViewTreeObserver();
        viewTreeObserver1.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                height = height - llTitle.getHeight() - getStatusHeight();//計算滑動的總距離
                stickyScrollView.setStickTop(llTitle.getHeight() + getStatusHeight());//設置距離多少懸浮
                //註意要移除
                llTitle.getViewTreeObserver()
                        .removeGlobalOnLayoutListener(this);
            }
        });


    }

    @Override
    public void onScrollChanged(int l, int t, int oldl, int oldt) {
        if (t <= 0) {
            llTitle.setBackgroundColor(Color.argb((int) 0, 255, 255, 255));

        } else if (t > 0 && t <= height) {
            float scale = (float) t / height;
            int alpha = (int) (255 * scale);
            llTitle.setBackgroundColor(Color.argb((int) alpha, 227, 29, 26));//設置標題欄的透明度及顏色
            StatusBarUtil.setTranslucentForImageView(MainActivity.this, alpha, title);//設置狀態欄的透明度
        } else { StatusBarUtil.setTranslucentForImageView(MainActivity.this, 0, title);
            llTitle.setBackgroundColor(Color.argb((int) 255, 227, 29, 26));
            StatusBarUtil.setTranslucentForImageView(MainActivity.this, 255, title);
        }
    }
}

 

        最後,尊重一下上述代碼的原作者,具體代碼可到github下載,https://github.com/xiaoyuanandroid/ProductPage。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在Arduino中,可以使用AnalogWrite來使用硬體產生490Hz/980Hz的pwm波,並可根據參數來設定占空比。不瞭解這個的同學可以去 "AnalogWrite" 學習下, "SecretsOfArduinoPWM" 也是講了Arduino在avr的定時/計數器上做的封裝,我們這裡並不講 ...
  • Linux文件和目錄的屬性及許可權 命令: [root@oldboy ~]# ls -lhi total 40K 24973 -rw-------. 1 root root 1.1K Dec 10 16:02 anaconda-ks.cfg 15 -rw-r--r--. 1 root root 22K... ...
  • Linux是單內核系統,可通用計算平臺的外圍設備是頻繁變化的,不可能將所有的(包括將來即將出現的)設備的驅動程式都一次性編譯進內核,為瞭解決這個問題,Linux提出了可載入內核模塊(Loadable Kernel Module,LKM)的概念,允許一個設備驅動通過模塊載入的方式,在內核運行起來之後" ...
  • 搭建環境:Centos6.5_x86_64,Zabbix2.4.5,epel 源 服務端: 1.安裝開發軟體包yum -y groupinstall "Development Tools" 2.安裝所需的依賴包yum -y install httpd mysql mysql-server mysql ...
  • ...
  • 原型設計模式: 用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。 原型設計模式簡單的來說,顧名思義, 不去創建新的對象進而保留原型的一種設計模式。 缺點:原型設計模式是的最主要的缺點就是這個克隆方法需要對類的功能進行檢測,這對於全新的類來說較容易,但對已有的類進行改造時將不是件容易的 ...
  • 轉自:http://www.jb51.net/article/42671.htm 在開始之前先說一點,DOM是非常容易理解的,但是大家說的太官方,讓人很是難於理解,我們就用非常簡單的語言翻譯一遍。加深對DOM的理解,從而對它有一個全面的認識。 什麼是DOM DOM的全稱是Document Objec ...
  • 謹記(指定選擇器Intent.createChooser()) 開始今天的內容前,先閑聊一下: (1)突然有一天頭腦風暴,對很多問題有了新的看法和見解,迫不及待的想要分享給大家,文檔已經寫好了,我需要通過微信或者QQ,簡訊等社交工具發送給大家。 (2)在網上發現一段特別好的文章,想要保存收藏下來。 ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...