SystemUI 拖拽事件分析

求你指教我們?cè)鯓訑?shù)算自己的日子骄崩,好叫我們得著智慧的心。----詩(shī)篇90:12

之前寫(xiě)過(guò)兩篇關(guān)于SystemUI的文章:
SystemUI之功能介紹和UI布局實(shí)現(xiàn)
SystemUI之呈現(xiàn)流程
本篇分析下SystemUI 拖拽事件處理的過(guò)程葛峻。

他山之石可以攻玉锹雏,通過(guò)本篇的分析力求能觸摸到Android團(tuán)隊(duì)對(duì)復(fù)雜view的處理技巧,以便今后我們也能在自己的項(xiàng)目里運(yùn)用上這些技巧术奖。
著重分析下面幾個(gè)知識(shí)點(diǎn)

  • 自定義View的高效布局方式,onMesure,onLayout—onDraw如何實(shí)現(xiàn)技巧
  • onTouchEvent—onIntecept—onDispach如何運(yùn)用礁遵,手勢(shì)監(jiān)聽(tīng)處理邏輯
  • 代碼的封裝性

開(kāi)胃小菜---點(diǎn)擊事件

如果對(duì)SystemUI布局結(jié)構(gòu)不了解,請(qǐng)先參考之前的文章SystemUI之功能介紹和UI布局實(shí)現(xiàn) 采记,我們先挑個(gè)軟柿子捏捏佣耐,看看下圖示意的點(diǎn)擊事件是如何處理的。
這里寫(xiě)圖片描述
在放上SystemUI的布局圖

這里寫(xiě)圖片描述

這里主要分析兩塊:

點(diǎn)擊頂部唧龄,如何控制狀態(tài)欄伸縮

根據(jù)SystemUI的布局圖兼砖,很容易找到點(diǎn)擊事件入口是在NotificationPanelView的onClick里。

@Override
public void onClick(View v) {
        if (v == mHeader) {
            onQsExpansionStarted();
            if (mQsExpanded) {
                flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
            } else if (mQsExpansionEnabled) {
                EventLogTags.writeSysuiLockscreenGesture(
                        EventLogConstants.SYSUI_TAP_TO_OPEN_QS,
                        0, 0);
                flingSettings(0 /* vel */, true /* expand */, null, true /* isClick */);
            }
      }
}

主要的事件處理被封裝在了flingSettings方法中既棺,

private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable,
            boolean isClick) {
        float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
        //忽略非主要代碼
        ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
        if (isClick) {
            animator.setInterpolator(mTouchResponseInterpolator);
            animator.setDuration(368);
        } else {
            mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
        }
        //忽略非主要代碼
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                setQsExpansion((Float) animation.getAnimatedValue());
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mScrollView.setBlockFlinging(false);
                mScrollYOverride = -1;
                mQsExpansionAnimator = null;
                if (onFinishRunnable != null) {
                    onFinishRunnable.run();
                }
            }
        });
        animator.start();
        mQsExpansionAnimator = animator;
        mQsAnimatorExpand = expand;
    }

這里使用屬性動(dòng)畫(huà)在onAnimationUpdate回調(diào)里控制狀態(tài)欄收縮讽挟,設(shè)置了addUpdateListener監(jiān)聽(tīng)器監(jiān)聽(tīng)動(dòng)畫(huà)執(zhí)行過(guò)程中值的變化,同時(shí)設(shè)置AnimatorListenerAdapter監(jiān)聽(tīng)動(dòng)畫(huà)結(jié)束丸冕。

Tips:
如果只需要監(jiān)聽(tīng)動(dòng)畫(huà)的某一個(gè)事件耽梅,比如結(jié)束事件,應(yīng)該設(shè)置AnimatorListenerAdapter監(jiān)聽(tīng)器胖烛,這樣就只用實(shí)現(xiàn)需要的事件眼姐,如果設(shè)置的是AnimatorListener監(jiān)聽(tīng)器诅迷,那么就不得不全部復(fù)寫(xiě)onAnimationStart/onAnimationRepeat/onAnimationEnd等回調(diào)事件,即使你只想要監(jiān)聽(tīng)其中的一個(gè)回調(diào)事件。

在onAnimationUpdate回調(diào)里众旗,可以拿到狀態(tài)欄的當(dāng)前高度罢杉,再來(lái)看看
setQsExpansion((Float) animation.getAnimatedValue())的執(zhí)行情況,該方法又調(diào)用setQsTranslation(height)方法贡歧,在其中調(diào)用了mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation())
語(yǔ)句滩租,這個(gè)也就是狀態(tài)欄的伸縮實(shí)現(xiàn)。

頂部view里的設(shè)置艘款、時(shí)鐘小圖標(biāo)如何跟隨變化

頂部view里內(nèi)容的變換同樣也是在NotificationPanelView的setQsExpansion方法中實(shí)現(xiàn)。

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java

private void setQsExpansion(float height) {
        height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
        mQsFullyExpanded = height == mQsMaxExpansionHeight;
        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
            setQsExpanded(true);
        } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
            setQsExpanded(false);
            if (mLastAnnouncementWasQuickSettings && !mTracking && !isCollapsing()) {
                announceForAccessibility(getKeyguardOrLockScreenString());
                mLastAnnouncementWasQuickSettings = false;
            }
        }
        mQsExpansionHeight = height;
        mHeader.setExpansion(getHeaderExpansionFraction());
        setQsTranslation(height);
        ...

先調(diào)用setQsExpanded(boolean expanded)方法沃琅,最終通過(guò)動(dòng)態(tài)更改布局參數(shù)哗咆,達(dá)到頂部view的整體收縮和拉伸。
調(diào)用方法鏈如下:

setQsExpanded---->
updateQsState---->
StatusBarHeaderView.setExpanded---->
StatusBarHeaderView.updateEverything---->
StatusBarHeaderView.updateHeights.

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java

private void updateHeights() {
        int height = mExpanded ? mExpandedHeight : mCollapsedHeight;
        ViewGroup.LayoutParams lp = getLayoutParams();
        if (lp.height != height) {
            lp.height = height;
            setLayoutParams(lp);
        }
    }

頂部view整體的收縮看完了益眉,在關(guān)注下頂部View的一個(gè)細(xì)節(jié)---MaterialDesign風(fēng)格的立體效果是如何實(shí)現(xiàn)的晌柬。
StatusBarHeaderView.setExpansion-->StatusBarHeaderView.setExpansion-->StatusBarHeaderView.setClipping

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java

private void setClipping(float height) {
        mClipBounds.set(getPaddingLeft(), 0, getWidth() - getPaddingRight(), (int) height);
        setClipBounds(mClipBounds);
        invalidateOutline();
    }

接著在分析內(nèi)部小控件是如何變換的。同樣從setExpansion看起郭脂。
setExpansion-->updateLayoutValues-->StatusBarHeaderView$LayoutValues.interpoloate-->applyLayoutValues
上面這條調(diào)用關(guān)系鏈都在StatusBarHeaderView里實(shí)現(xiàn)年碘。看下interpoloate和applyLayoutValues方法

private static final class LayoutValues {
    float timeScale = 1f;
        float clockY;
        float dateY;
        ...
        public void interpoloate(LayoutValues v1, LayoutValues v2, float t) {
            timeScale = v1.timeScale * (1 - t) + v2.timeScale * t;
            clockY = v1.clockY * (1 - t) + v2.clockY * t;
            dateY = v1.dateY * (1 - t) + v2.dateY * t;
            ...
        }
}
 private void applyLayoutValues(LayoutValues values) {
        mTime.setScaleX(values.timeScale);
        mTime.setScaleY(values.timeScale);
        mClock.setY(values.clockY - mClock.getHeight());
        mDateGroup.setY(values.dateY);

interpoloate方法先計(jì)算出縮放比例和透明度比例展鸡,然后在applyLayoutValues對(duì)控件做縮放處理屿衅。
以上分析完了狀態(tài)欄伸縮的實(shí)現(xiàn)。其分析時(shí)用的代碼基于Android5.0莹弊。Android7.0上SystemUI狀態(tài)欄又發(fā)生了變化涤久。

Android7.0上SystemUI拖拽實(shí)現(xiàn)

我們先看看Android7.0上SystemUI拖拽時(shí)的樣子。


這里寫(xiě)圖片描述

可以看到Android7.0上向上拖拽時(shí)忍弛,快捷小圖標(biāo)非常炫酷移動(dòng)效果响迂,下面來(lái)看看其如何實(shí)現(xiàn)。
根據(jù)SystemUI的布局圖快捷小圖標(biāo)的父類(lèi)視圖為QSContainer细疚,因此小圖標(biāo)的變化很可能在其中實(shí)現(xiàn)蔗彤,查看其中的方法,在onFinishInflate()方法中有一個(gè)QSAnimator對(duì)象疯兼,onFinishInflate()方法在視圖全部加載完成后會(huì)調(diào)用然遏,而QSAnimator在SystemUI中是QuickSettingAnimator的縮寫(xiě),這樣看來(lái)動(dòng)畫(huà)的實(shí)現(xiàn)多半是在QSAnimator中實(shí)現(xiàn)吧彪。

frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java

    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
            int oldTop, int oldRight, int oldBottom) {
        mQsPanel.post(mUpdateAnimators);
    }

繼續(xù)跟蹤mUpdateAnimators來(lái)到了updateAnimators(),

private void updateAnimators() {
    //...
    for (QSTile<?> tile : tiles) {
        //...
        if (count < mNumQuickTiles && mAllowFancy) {
                //...
                    // Move the quick tile right from its location to the new one.
                translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff);
                translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);

                // Counteract the parent translation on the tile. So we have a static base to
                // animate the label position off from.
                firstPageBuilder.addFloat(tileView, "translationY", mQsPanel.getHeight(), 0);

                // Move the real tile's label from the quick tile position to its final
                // location.
                translationXBuilder.addFloat(label, "translationX", -xDiff, 0);
                translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
                //...
        }
    }
    if (mAllowFancy) {
        //...
        PathInterpolatorBuilder interpolatorBuilder = new PathInterpolatorBuilder(0, 0, 0, 1);
        translationXBuilder.setInterpolator(interpolatorBuilder.getXInterpolator());
        translationYBuilder.setInterpolator(interpolatorBuilder.getYInterpolator());
        mTranslationXAnimator = translationXBuilder.build();
        mTranslationYAnimator = translationYBuilder.build();
    }
}

以上代碼通過(guò)mNumQuickTiles來(lái)確定動(dòng)畫(huà)結(jié)束后小圖標(biāo)的個(gè)數(shù)啦鸣,默認(rèn)為5,可以同過(guò)對(duì)settings數(shù)據(jù)庫(kù)中的sysui_qqs_count字段來(lái)配置来氧,而mAllowFancy決定是否開(kāi)啟動(dòng)畫(huà)效果诫给。
來(lái)看看將mNumQuickTiles設(shè)置成7香拉,關(guān)閉mAllowFancy后的效果


這里寫(xiě)圖片描述

Tips:
更改settings數(shù)據(jù)庫(kù)中某個(gè)字段的值,可以用類(lèi)似如下的快捷方式:
adb shell settings put secure sysui_qqs_count 7

以上我們理清了Android7.0上拖拽動(dòng)畫(huà)的實(shí)現(xiàn)過(guò)程中狂。細(xì)節(jié)方面還有一些疑惑凫碌。

動(dòng)畫(huà)是如何動(dòng)起來(lái)的

translationXBuilder是TouchAnimator類(lèi)中的一個(gè)靜態(tài)類(lèi)Builder,其build()方法返回的是一個(gè)TouchAnimator對(duì)象。
frameworks/base/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java

public class TouchAnimator {
        public static class Builder {
            //...
            public TouchAnimator build() {
                return new TouchAnimator(mTargets.toArray(new Object[mTargets.size()]),
                        mValues.toArray(new KeyframeSet[mValues.size()]),
                        mStartDelay, mEndDelay, mInterpolator, mListener);
            }
        }
}

TouchAnimator是對(duì)動(dòng)畫(huà)類(lèi)的封裝胃榕,而其內(nèi)建的Builder又是對(duì)動(dòng)畫(huà)參數(shù)的配置盛险,那么問(wèn)題來(lái)了,build方法直接返回了一個(gè)TouchAnimator對(duì)象勋又,并沒(méi)有看到其start動(dòng)畫(huà)苦掘,動(dòng)畫(huà)的所有參數(shù)已經(jīng)配置好了,其已經(jīng)處于就緒狀態(tài)楔壤,它在何處被start呢鹤啡?
為了弄清楚translationXBuilder到底如何工作的,在回到updateAnimators方法中蹲嚣,看看
translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff);
到底做了什么递瑰。

public Builder addFloat(Object target, String property, float... values) {
    add(target, KeyframeSet.ofFloat(getProperty(target, property, float.class), values));
    return this;
}

這里的getProperty是個(gè)什么鬼

private static Property getProperty(Object target, String property, Class<?> cls) {
        if (target instanceof View) {
            switch (property) {
                case "translationX":
                    return View.TRANSLATION_X;
                case "translationY":
                    return View.TRANSLATION_Y;
                case "translationZ":
                    return View.TRANSLATION_Z;
                case "alpha":
                    return View.ALPHA;
                case "rotation":
                    return View.ROTATION;
                case "x":
                    return View.X;
                case "y":
                    return View.Y;
                case "scaleX":
                    return View.SCALE_X;
                case "scaleY":
                    return View.SCALE_Y;
            }
        }
        if (target instanceof TouchAnimator && "position".equals(property)) {
            return POSITION;
        }
        return Property.of(target.getClass(), cls, property);
}

這種用法還第一次見(jiàn)到,厲害了我的谷歌哥隙畜!

我們傳入的是quickTileView抖部,getProperty根據(jù)屬性返回給了對(duì)應(yīng)的View.TRANSLATION_X,接著KeyframeSet.ofFloat new出一個(gè)FloatKeyframeSet對(duì)象议惰,最后傳入的quickTileView對(duì)象被存放在mTargets list中慎颗,F(xiàn)loatKeyframeSet對(duì)象被存放在mValues list中。

view有了言询,動(dòng)畫(huà)屬性也設(shè)置進(jìn)來(lái)了哗总,最后動(dòng)畫(huà)屬性如何被設(shè)置到view上呢?原來(lái)動(dòng)畫(huà)設(shè)置被隱藏在FloatKeyframeSet中

@Override
protected void interpolate(int index, float amount, Object target) {
    float firstFloat = mValues[index - 1];
    float secondFloat = mValues[index];
    mProperty.set((T) target, firstFloat + (secondFloat - firstFloat) * amount);
}

關(guān)鍵的mProperty.set語(yǔ)句實(shí)際上就相當(dāng)于:

View.TRANSLATION_X.set(view, 100f);

它的主要調(diào)用過(guò)程如下:

NotificationPanelView.updateQsExpansion
---->QSContainer.setQsExpansion
---->QSAnimator.setPosition(expansion)
---->TouchAnimator.setPosition(position)
---->mKeyframeSets[i].setValue(t, mTargets[i])
---->mProperty.set((T) target, firstFloat + (secondFloat - firstFloat) * amount);

后記

本篇博文的前半部分實(shí)際上早幾個(gè)月已經(jīng)完成了倍试,當(dāng)時(shí)計(jì)劃本篇重點(diǎn)要闡述SystemUI的主體框架以及其中精妙的代碼設(shè)計(jì)讯屈。UI上的拖拽動(dòng)畫(huà)只是作為開(kāi)胃小菜順帶入題用的。但計(jì)劃總被各種事情打斷县习,當(dāng)前也早已經(jīng)不負(fù)責(zé)SystemUI模塊的問(wèn)題了涮母,UI拖拽已經(jīng)占據(jù)了大部分篇幅,如果在介紹框架跟設(shè)計(jì)躁愿,恐怕篇幅會(huì)又臭又長(zhǎng)叛本。自己能力跟精力有限,本篇只好草草收?qǐng)觥?/p>

寫(xiě)作的過(guò)程糾結(jié)無(wú)比彤钟,想推倒重新再來(lái)来候,卻又不甘心放棄已經(jīng)寫(xiě)成的前半部分。所謂"食之無(wú)味逸雹,棄之可惜"营搅≡菩恐怕讀的人也感覺(jué)無(wú)趣。希望讀的有心人能多提些好的寫(xiě)作建議转质,不甚感激园欣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市休蟹,隨后出現(xiàn)的幾起案子沸枯,更是在濱河造成了極大的恐慌,老刑警劉巖赂弓,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绑榴,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡盈魁,警方通過(guò)查閱死者的電腦和手機(jī)翔怎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)备埃,“玉大人姓惑,你說(shuō)我怎么就攤上這事褐奴“唇牛” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵敦冬,是天一觀(guān)的道長(zhǎng)辅搬。 經(jīng)常有香客問(wèn)我,道長(zhǎng)脖旱,這世上最難降的妖魔是什么堪遂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮萌庆,結(jié)果婚禮上溶褪,老公的妹妹穿的比我還像新娘。我一直安慰自己践险,他們只是感情好猿妈,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著巍虫,像睡著了一般彭则。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上占遥,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天俯抖,我揣著相機(jī)與錄音,去河邊找鬼瓦胎。 笑死芬萍,一個(gè)胖子當(dāng)著我的面吹牛尤揣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播担忧,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼芹缔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了瓶盛?” 一聲冷哼從身側(cè)響起最欠,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惩猫,沒(méi)想到半個(gè)月后芝硬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡轧房,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年拌阴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奶镶。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡迟赃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厂镇,到底是詐尸還是另有隱情纤壁,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布捺信,位于F島的核電站酌媒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏迄靠。R本人自食惡果不足惜秒咨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望掌挚。 院中可真熱鬧雨席,春花似錦、人聲如沸吠式。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)奇徒。三九已至雏亚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摩钙,已是汗流浹背罢低。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人网持。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓宜岛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親功舀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子萍倡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,262評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件辟汰、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,117評(píng)論 4 61
  • 工作也已許久列敲。渾渾噩噩,一方面工作上的能力得以提升帖汞,可當(dāng)?shù)玫绞裁吹臅r(shí)候也許正失去著什么戴而。 也許是昨天,也許是更早的...
    txqdx閱讀 232評(píng)論 0 0
  • 卡方適合性檢驗(yàn)的目的 卡方適合性檢驗(yàn)的目的是為了檢查所抽取的樣本是符合與預(yù)期值如我們對(duì)學(xué)生群體進(jìn)行抽樣調(diào)查翩蘸,需要對(duì)...
    我叫大濕兄閱讀 6,515評(píng)論 0 1
  • 蟬鳴不驚風(fēng)所意, 金烏枯青蓬。 飲水無(wú)盡時(shí)催首, 常念雪滿(mǎn)城扶踊。
    無(wú)心詩(shī)人閱讀 162評(píng)論 0 1