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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...