這可能是最"俗"的View的事件分發(fā)機(jī)制(含Demo講解)

本文將通過三個(gè)demo來讓你深刻感受到了解View的事件分發(fā)機(jī)制之后你能做什么,能做好什么??!

首先我們裝作這些概念都理解了:(下文詳細(xì)介紹)

觸摸事件類型: 主要類型三種:
ACTION_DOWN
ACTION_MOVE
ACTION_UP

完整的事件傳遞主要包括三個(gè)階段: 事件的分發(fā),攔截和消費(fèi)

分發(fā):對應(yīng)dispatchTouchEvent方法。返回true表示事件被當(dāng)前視圖消費(fèi),不再繼續(xù)分發(fā)

攔截:對應(yīng)onInterceptTouchEvent方法。返回true表示攔截此事件,不繼續(xù)分發(fā)。(viewGroup和其子類中才擁有)
消費(fèi):對應(yīng)onTouchEvent方法,返回true表示消費(fèi)事件,不在向上傳遞。

view的事件傳遞機(jī)制:
—— 觸摸事件的傳遞流程是從dispatchTouchEvent開始的,如果我們不進(jìn)行重寫(也就是返回默認(rèn)的父類同名函數(shù)),則事件將會(huì)依照嵌套層次從外層向內(nèi)層傳遞,到底最內(nèi)層的View時(shí),就交給它的onTouchEvent處理,該方法如果能消費(fèi)該事件,則返回true,如果處理不了,則返回false,這時(shí)事件會(huì)重新向外傳遞。并由外層的onTouchEvent處理,依此類推

—— 如果事件在向內(nèi)層傳遞的過程中被我們重寫事件處理函數(shù)返回true時(shí),則會(huì)導(dǎo)致整個(gè)事件提前被消費(fèi),內(nèi)層View不會(huì)收到這個(gè)事件了。

—— View控件的事件觸發(fā)順序是先執(zhí)行onTouch方法,最后才執(zhí)行onClick方法。如果onTouch方法返回true的話,則事件將不會(huì)繼續(xù)傳遞,最后也不會(huì)調(diào)用onClick方法,如果onTouch返回false,則繼續(xù)向下傳遞。因?yàn)閎utton的preformClick是利用onTouchEvent實(shí)現(xiàn)的,假設(shè)onTouchEvent沒有被調(diào)用到,那么點(diǎn)擊事件就無效了。

viewGroup的事件傳遞機(jī)制:

—— 觸摸事件的傳遞順序是由Activity到ViewGroup,再由ViewGrop遞歸傳遞給它的子View。

—— ViewGroup通過onInterceptTouchEvent方法對事件進(jìn)行攔截,如果該方法返回true,則事件不會(huì)繼續(xù)傳遞給子View,如果返回false或者super.onInterceptTouchEvent,則事件會(huì)繼續(xù)傳遞給子View。

—— 在子View對事件進(jìn)行消費(fèi)后,ViewGroup將接收不到任何事件。

臥槽,這些概念我在別的地方也看到過呀,你這也不是就bibi一些概念嗎,可是到底怎么用,用在哪里呢,這些概念表達(dá)的意思又到底是個(gè)啥呀?

咱們舉個(gè)例子哈,在一個(gè)美好的早晨,一家子人都起來啦,打開門迎接美好的一天,突然天上掉下一個(gè)餡餅,還是金的,掉在你的祖爺爺面前。(金餡餅就是我們的事件),這時(shí)大家就聚在一起啊,你的祖爺爺(activity)輩分最大,餡餅也是掉他那的,先擁有這個(gè)餡餅的分配權(quán),你的祖爺爺非常愛你們,他說這餡餅啊,我都不久于人世了用不著,留給我的寶貝兒子吧(ViewGroup),他兒子不就是你爺爺嗎,你爺爺也愛你爸啊,就又給了你爸,你爸最后給了你,這時(shí)你就開心了,我拿到了這個(gè)金餡餅,那我是留著還是留著?這時(shí)你非常激動(dòng)啊,你想著自己還沒娶媳婦,你就說那恭敬不如從命了,你拿著餡餅娶了一個(gè)漂亮能干的媳婦(消費(fèi)掉了事件,onTouchEvent返回true),那這個(gè)事情就結(jié)束了。當(dāng)然,還有一種情況,你已經(jīng)有漂亮媳婦了,不需要了,你覺得應(yīng)該孝順長輩,你又跟你爸說,我不用啊,這金餅給我也沒用,我有大金鏈子,你身體不好自己拿著看大夫吧。然后你爸拿著一想孩子說的沒錯(cuò),就自己拿去治病了(消費(fèi)掉了)。
一個(gè)餡餅由一次觸摸事件的ACTION_DOWN開始,最先拿到的是Activity(Window),然后一層一層往下分發(fā)(dispatchTouchEvent),如果有誰需要拿到這個(gè)金餡餅干啥,他就攔截掉(onInterceptTouchEvent),那么備份最小的你(View)就根本摸不到這個(gè)金餡餅了,如果沒有ViewGroup(比你輩分大的爺爺,爸爸等)攔截,都想給你娶媳婦,,那么你就拿到了這個(gè)金餡餅,先調(diào)用你的onTouchEvent事件,你確實(shí)不需要啊,然后又一層一層返回去,一層層調(diào)用onTouchEvent,看誰需要,大致就這么一個(gè)邏輯。

哇靠,你這么說我似乎優(yōu)點(diǎn)明白了,但咱能不拿這個(gè)餡餅說事嗎,我開發(fā)又不是寫?zhàn)W餅,能不拿舉個(gè)別的例子啊。好的,客官你別急,這就給你上菜。

滑動(dòng)沖突想必是在開發(fā)中老生常談的問題了,只要我們內(nèi)部View和外部View都能滑動(dòng),那么必定就會(huì)存在滑動(dòng)沖突,我們想要處理的話,就需要用到我們的事件分發(fā)知識(shí)啦。而通常我們處理滑動(dòng)沖突分為兩種,分別叫做外部攔截法和內(nèi)部攔截法,比如我們一個(gè)ViewPager中嵌套了一個(gè)RecycleView或ListView,滑動(dòng)時(shí)非常的不爽,安卓并不知道是具體誰要處理這個(gè)事件,金餅就一塊,我到底給誰啊,你們好幾個(gè)人都要。那我肯定需要添加一些條件了,看看到底是給誰啊,外部攔截就是重寫父容器的onInterceptTouchEvent()方法,因?yàn)檫@塊金餅先到的還是長輩手里,這個(gè)時(shí)候你就要 處理好啊,我到底是留給自己處理ViewPager的左右滑動(dòng)呢,還是處理ListView的上下滑動(dòng)呀,你只需要比較在X軸和Y軸移動(dòng)的距離,如果X軸大于Y,那就是左右滑動(dòng),就把這塊金餅直接攔截掉消費(fèi)了,就不給ListView了,如果X小于Y,那就是上下滑動(dòng)了,你就不攔截,把金餅給ListView消費(fèi)。

 public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int)event.getX();
int y = (int)event.getY();
switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN: {
        intercepted = false;
       break;
   }
   case MotionEvent.ACTION_MOVE: {
       if (滿足父容器的攔截要求) {
            intercepted = true;
       } else {
            intercepted = false;
       }
       break;
   }
   case MotionEvent.ACTION_UP: {
       intercepted = false;
       break;
   }
   default:
       break;
   }
        mLastXIntercept = x;
        mLastYIntercept = y;
        return intercepted;}

以上是外部攔截法的模板代碼,針對不同的滑動(dòng)沖突,只需要修改父容器需要攔截當(dāng)前事件這個(gè)條件即可,其他均不需做修改并且也不能修改,在onInterceptTouchEvent方法中,首先是ACTION_DOWN這個(gè)事件,父容器絕大部分情況下必須返回false,即不攔截ACTION_DOWN事件,這是因?yàn)橐坏└溉萜鲾r截了ACTION_DOWN,那么后續(xù)的ACTION_MOVE和ACTION_UP事件都會(huì)直接交給父容器處理,這個(gè)時(shí)候事件沒法再傳遞給子元素了;其次是ACTION_MOVE事件,這個(gè)事件可以根據(jù)需要來決定是否攔截,如果父容器需要攔截就返回true,否則返回false;最后是ACTION_UP事件,這里必須要返回false,因?yàn)锳CTION_UP事件本身沒有太多意義。

重點(diǎn)?。?!

臥槽,那玩意我是一個(gè)ViewPager嵌套一個(gè)ViewPager呢,兩個(gè)都是水平方向的滑動(dòng),這個(gè)我要怎么判斷啊,這個(gè).....這個(gè)貌似這種方式行不通吧,好像比較難啊,怎么去獲取判斷的條件啊,依據(jù)是什么呀。

ViewPager嵌套

不要怕不要驚慌啊,我們肯定是可以解決的。我們外部攔截法行不通有沒有內(nèi)部攔截法,自然是有的,內(nèi)部攔截法其實(shí)就是重寫子元素的dispatchTouchEvent()方法,并調(diào)用getParent().requestDisallowInterceptTouchEvent(true)父容器不能攔截子元素需要的事件。用我們的餡餅來說就是不管有多少長輩(ViewGroup父容器),餡餅都應(yīng)該是先給你的(子元素),你擁有燒餅的最先處理權(quán),如果你需要消費(fèi)它那你就直接消費(fèi)掉,不需要再交給父容器處理。但是我們事件dispatchTouchEvent是由父輩們一層一層分發(fā)下來的,萬一哪個(gè)中間擺你一道,把餡餅?zāi)萌セǖ袅四?,為了預(yù)防這種情況,我們就需要配合getParent().requestDisallowInterceptTouchEvent(true)來事先通知他們不可以攔截。

首先是子元素的dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent event) {
...

switch (action) {
  case MotionEvent.ACTION_MOVE:
        getParent().requestDisallowInterceptTouchEvent(true);

    break;
  case MotionEvent.ACTION_MOVE:
    if(父容器需要處理此事件)
          getParent().requestDisallowInterceptTouchEvent(false);


    break;
  case MotionEvent.ACTION_UP: {
    break;
}
...
return super.dispatchTouchEvent(event); }

這事件我們還要修改父容器的onInterceptTouchEvent()方法,代碼如下:

@Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {

  int action=ev.getAction();
  if(action==MotionEvent.ACTION_DOWN){
    return false;
  }else {
    return true;
  }
}

父容器攔截了除了DOWN事件以外的其他事件,這樣當(dāng)子元素調(diào)用parent.requestDisallowInterceptTouchEvent(false)方法時(shí),父元素才能繼續(xù)攔截所需的事件。當(dāng)返回true時(shí),不分發(fā)到子元素,并執(zhí)行自己的onTouch方法。onInterceptTouchEvent()方法默認(rèn)是不攔截的,所以我們需要考慮到,當(dāng)子元素不處理時(shí),我們需要父元素(外層ViewPager來處理),所以我們才會(huì)重寫父容器的onInterceptTouchEvent方法。

現(xiàn)在相信大家對于安卓Touch事件有了一個(gè)相對還比較清晰的了解了,至少知道他們的一個(gè)事件流向,分發(fā),攔截以及消費(fèi),這里在安卓開發(fā)探索一書中總結(jié)得特別好,大致如下:

1:同一個(gè)事件序列是指手機(jī)接觸屏幕那一刻起,到離開屏幕那一刻結(jié)束,有一個(gè)down事件,若干個(gè)move事件,一個(gè)up事件構(gòu)成。

2:某個(gè)View一旦決定攔截事件,那么這個(gè)事件序列之后的事件都會(huì)由它來處理,并且不會(huì)再調(diào)用onInterceptTouchEvent。

3:正常情況下,一個(gè)事件序列只能被一個(gè)View攔截并消耗。這個(gè)原因可以參考第2條,因?yàn)橐坏r截了某個(gè)事件,那么這個(gè)事件序列里的其他事件都會(huì)交給這個(gè)View來處理,所以同一事件序列中的事件不能分別由兩個(gè)View同時(shí)處理,但是我們可以通過特殊手段做到,比如一個(gè)View將本該自己處理的事件通過onTouchEvent強(qiáng)行傳遞給其他View處理。

4:一個(gè)View如果開始處理事件,如果它不處理down事件(onTouchEvent里面返回了false),那么這個(gè)事件序列的其他事件就不會(huì)交給它來繼續(xù)處理了,而是會(huì)交給它的父元素去處理。

5:如果一個(gè)View處理了down事件,卻沒有處理其他事件,那么這些事件不會(huì)交給父元素處理,并且這個(gè)View還能繼續(xù)受到后續(xù)的事件。而這些未處理的事件,最終會(huì)交給Activity來處理。

6:ViewGroup的onInterceptToucheEvent默認(rèn)返回false,也就是默認(rèn)不攔截事件。

7:View沒有InterceptTouchEvent方法,如果有事件傳過來,就會(huì)直接調(diào)用onTouchEvent方法。

8:View的onTouchEvent方法默認(rèn)都會(huì)消耗事件,也就是默認(rèn)返回true,除非他是不可點(diǎn)擊的(longClickable和clickable同時(shí)為false)。

9:View的enable屬性不會(huì)影響onTouchEvent的默認(rèn)返回值。就算一個(gè)View是不可見的,只要他是可點(diǎn)擊的(clickable或者longClickable有一個(gè)為true),它的onTouchEvent默認(rèn)返回值也是true。

10:onClick方法會(huì)執(zhí)行的前提是當(dāng)前View是可點(diǎn)擊的,并且它收到了down和up事件。

11:事件傳遞過程是由外向內(nèi)的,也就是事件會(huì)先傳給父元素在向下傳遞給子元素。但是子元素可以通過requestDisallowInterceptTouchEvent來干預(yù)父元素的分發(fā)過程,但是down事件除外(因?yàn)閐own事件方法里,會(huì)清除所有的標(biāo)志位)。

我們用偽代碼表示一下分發(fā),攔截和消費(fèi)的關(guān)系:

  public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;
    if (onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else {
        consume = child.dispatchTouchEvent(ev);
    }
    return consume
}

如果以上知識(shí),你基本了解清楚了,那么針對各種情況下的滑動(dòng)沖突,你都能處理了,只是判斷的邏輯不同而已。這個(gè)就需要自己多加練習(xí)了,最近我也會(huì)不斷補(bǔ)強(qiáng)這一塊的知識(shí),在實(shí)踐中不斷成長。

那么本文到此就結(jié)束了嗎,View的事件分發(fā)機(jī)制就是用來處理滑動(dòng)沖突的嗎,隨便這已經(jīng)很了不起了但他能做的遠(yuǎn)遠(yuǎn)不止這些。

當(dāng)我們的設(shè)計(jì)師用想象力沖破天機(jī)的思維告訴你想要什么什么樣的交互效果,我的內(nèi)心是崩潰的

那么通常這樣的交互效果需要我們時(shí)刻追蹤著用戶在屏幕上的一舉一動(dòng),然后獲取到用戶操作的坐標(biāo),通過動(dòng)畫等產(chǎn)生特定的效果,讓我們的用戶有一個(gè)爽歪歪的交互體驗(yàn)。

這里我們就實(shí)現(xiàn)一個(gè)最簡單的下拉頭部圖片放大,松手時(shí)自動(dòng)回彈的ScrollView吧。

回彈.gif

臥槽我也不知道為什么錄制的gif這么小,反正大概就這樣子。

拿到這樣一個(gè)需求我們首先要分析他,解剖一下。

(1)下拉時(shí)頭部變大
(2)松手后回彈,頭部回復(fù)大小
那么這兩個(gè)需求,可能用到哪些知識(shí)點(diǎn)來實(shí)現(xiàn)呢?
1:記錄下拉的值,下拉越大,頭部倍數(shù)越大,在哪里一直記錄這個(gè)下拉值,自然就是我們今天學(xué)的咯,我們可以在dispatchTouchEvent或者onTouchEvent中獲取到我們觸摸點(diǎn)的坐標(biāo)
2:View頭部的變大和回彈,需要?jiǎng)赢媮磉_(dá)到一個(gè)流暢順滑有彈性的效果,而我們的補(bǔ)間代碼似乎無法滿足此類要求,所以我們需要考慮用到一個(gè)ValueAnimator動(dòng)畫,通過改變View對象的屬性來實(shí)現(xiàn)動(dòng)畫效果。
3:放大應(yīng)該有一個(gè)最大倍數(shù),不可能無限放大,那太難看了
4:自定義ScrollView的話,我們需要獲取到ScrollView中的頭部這個(gè)View
5:縮放的話我們應(yīng)該通過設(shè)置頭部LayoutParam改變

下面我就直接貼代碼了,大家可以自己參考一下:

public class PullBackScrollView extends ScrollView {

private View mHeaderView;
private int mHeaderWidth;
private int mHeaderHeight;

// 是否正在下拉
private boolean mIsPulling;

private int mLastY;

// 最大的放大倍數(shù)
private float mScaleTimes = 2.0f;
// 滑動(dòng)放大系數(shù):系數(shù)越大,滑動(dòng)時(shí)放大程度越大
private float mScaleRatio = 0.4f;
// 回彈時(shí)間系數(shù):系數(shù)越小,回彈越快
private float mReplyRatio = 0.5f;

// 當(dāng)前坐標(biāo)值
private float currentX = 0;
private float currentY = 0;
// 移動(dòng)坐標(biāo)值
private float distanceX = 0;
private float distanceY = 0;
// 最后坐標(biāo)值
private float lastX = 0;
private float lastY = 0;
// 上下滑動(dòng)標(biāo)記
private boolean upDownSlide = false;

public static final String TAG = "PullBackScrollView";


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

public PullBackScrollView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

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

@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    // 設(shè)置不可過度滾動(dòng),否則上移后下拉會(huì)出現(xiàn)部分空白的情況
    setOverScrollMode(OVER_SCROLL_NEVER);
    View child = getChildAt(0);
    if (child != null && child instanceof ViewGroup) {
        // 獲取默認(rèn)第一個(gè)子View
        ViewGroup vg = (ViewGroup) getChildAt(0);
        if (vg.getChildAt(0) != null) {
            mHeaderView = vg.getChildAt(0);//此時(shí)headView為activity_header.xml中的RelativeLayout

        }
    }
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mHeaderWidth = mHeaderView.getMeasuredWidth();
    mHeaderHeight = mHeaderView.getMeasuredHeight();
}

//重寫事件分發(fā)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    currentX = ev.getX();
    currentY = ev.getY();

    switch (ev.getAction()) {
        case MotionEvent.ACTION_MOVE:
            distanceX = currentX - lastX;
            distanceY = currentY - lastY;
            if (Math.abs(distanceX) < Math.abs(distanceY) && Math.abs(distanceY) > 12) {
                upDownSlide = true;
            }
            break;
    }

    lastX = currentX;
    lastY = currentY;

    if (upDownSlide && mHeaderView != null) {
        commOnTouchEvent(ev);
    }
    return super.dispatchTouchEvent(ev);
}

/**
 * @Description 觸摸事件
 */
private void commOnTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_UP:
            // 手指離開后頭部恢復(fù)圖片
            mIsPulling = false;
            replyView();
            clear();
            break;
        case MotionEvent.ACTION_MOVE:
            if (!mIsPulling) {
                // 第一次下拉
                if (getScrollY() == 0) {
                    // 滾動(dòng)到頂部時(shí)記錄位置,否則正常返回
                    mLastY = (int) ev.getY();
                } else {
                    break;
                }
            }

            int distance = (int) ((ev.getY() - mLastY) * mScaleRatio);
            // 當(dāng)前位置比記錄位置要小時(shí)正常返回
            if (distance < 0) {
                break;
            }
            mIsPulling = true;
            setZoom(distance);
            break;
    }
}

/**
 * @Description 頭部縮放
 */
private void setZoom(float s) {
    float scaleTimes = (float) ((mHeaderWidth + s) / (mHeaderWidth * 1.0));
    // 如超過最大放大倍數(shù)則直接返回
    if (scaleTimes > mScaleTimes) {
        return;
    }
    ViewGroup.LayoutParams layoutParams = mHeaderView.getLayoutParams();
    layoutParams.width = (int) (mHeaderWidth + s);
    layoutParams.height = (int) (mHeaderHeight * ((mHeaderWidth + s) / mHeaderWidth));
    // 設(shè)置控件水平居中
    ((MarginLayoutParams) layoutParams).setMargins(-(layoutParams.width - mHeaderWidth) / 2, 0, 0, 0);
    mHeaderView.setLayoutParams(layoutParams);
}

/**
 * @Description 回彈動(dòng)畫
 */
private void replyView() {
    final float distance = mHeaderView.getMeasuredWidth() - mHeaderWidth;
    // 設(shè)置動(dòng)畫
    ValueAnimator anim = ObjectAnimator.ofFloat(distance, 0.0F).setDuration((long) (distance * mReplyRatio));
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            setZoom((Float) animation.getAnimatedValue());
        }
    });
    anim.start();
}

/**
 * @Description 清除屬性值
 */
private void clear() {
    lastX = 0;
    lastY = 0;
    distanceX = 0;
    distanceY = 0;
    upDownSlide = false;
}}

在xml中引用我們的控件:

  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.pz.zoomscrollview.MainActivity">

<com.example.pz.zoomscrollview.PullBackScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <include layout="@layout/activity_header" />

        <include layout="@layout/activity_content" />

    </LinearLayout>

</com.example.pz.zoomscrollview.PullBackScrollView></LinearLayout>

xml中的代碼我就不全部貼出來了,自己隨便寫點(diǎn)啥都可以,喜歡美女的放張美女背景圖,喜歡跑車的放跑車。

這里自己可以只放一張圖片

按道理來說Activity代碼是什么都不需要寫的,你在xml中引用了自定義就好了,這里的話為了視覺效果,實(shí)現(xiàn)了一下沉浸式狀態(tài),就也貼出來參考一下實(shí)現(xiàn)。

public class MainActivity extends AppCompatActivity {

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

private void fullScreen(Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //5.x開始需要把顏色設(shè)置透明,否則導(dǎo)航欄會(huì)呈現(xiàn)系統(tǒng)默認(rèn)的淺灰色
            Window window = activity.getWindow();
            View decorView = window.getDecorView();
            //兩個(gè) flag 要結(jié)合使用,表示讓應(yīng)用的主體內(nèi)容占用系統(tǒng)狀態(tài)欄的空間
            int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
            decorView.setSystemUiVisibility(option);
                     window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(Color.TRANSPARENT);
            //導(dǎo)航欄顏色也可以正常設(shè)置
//                window.setNavigationBarColor(Color.TRANSPARENT);
        } else {
            Window window = activity.getWindow();
            WindowManager.LayoutParams attributes = window.getAttributes();
            int flagTranslucentStatus = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
            int flagTranslucentNavigation = WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
            attributes.flags |= flagTranslucentStatus;
//                attributes.flags |= flagTranslucentNavigation;
            window.setAttributes(attributes);
        }
    }
}}

本文可能有一些知識(shí)點(diǎn)并未闡述得特別詳細(xì),由于本人水平有限,也會(huì)在最近一段時(shí)間不短的學(xué)習(xí)相關(guān)知識(shí)以及更新一些相關(guān)文章,大家一起成長。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容