Android自定義ViewGroup實(shí)現(xiàn)九宮格布局
前言
在之前的文章我們復(fù)習(xí)了 ViewGroup 的測(cè)量與布局,那么我們這一篇效果就可以在之前的基礎(chǔ)上實(shí)現(xiàn)一個(gè)靈活的九宮格布局。
那么一個(gè)九宮格的 ViewGroup 如何定義,我們分解為如下的幾個(gè)步驟來(lái)實(shí)現(xiàn):
- 先計(jì)算與測(cè)量九宮格內(nèi)部的子View的寬度與高度。
- 再計(jì)算整體九宮格的寬度和高度。
- 進(jìn)行子View九宮格的布局。
- 對(duì)單獨(dú)的圖片和四宮格的圖片進(jìn)行單獨(dú)的布局處理
- 對(duì)填充的子View的方式進(jìn)行抽取,可以自由添加布局。
- 對(duì)自定義屬性的抽取,設(shè)置通用的屬性。
只要在前文的基礎(chǔ)上掌握了 ViewGroup 的測(cè)量與布局,其實(shí)實(shí)現(xiàn)起來(lái)一點(diǎn)都不難,甚至我們還能實(shí)現(xiàn)一些特別的效果。
好了,話(huà)不多說(shuō),Let's go
一、九宮格的測(cè)量
之前的文章,我們的測(cè)量方式是已經(jīng)知道子 View 的具體大小了,讓我們的父布局做寬高的適配,所以我們的邏輯順序也是先布局,然后再測(cè)量,對(duì) ViewGroup 的寬高做限制。
但是在我們做九宮格控件的時(shí)候,就和之前有所區(qū)別了。我們不管子 View 的寬高測(cè)量模式是怎樣的,我們都是通過(guò)九宮格控件的寬度對(duì)子 View 的寬高進(jìn)行強(qiáng)制賦值。
public class AbstractNineGridLayout extends ViewGroup { private static final int MAX_CHILDREN_COUNT = 9; //最大的子View數(shù)量 private int horizontalSpacing = 20; //每一個(gè)Item的左右間距 private int verticalSpacing = 20; //每一個(gè)Item的上下間距 private int itemWidth; private int itemHeight; public AbstractNineGridLayout(Context context) { this(context, null); } public AbstractNineGridLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public AbstractNineGridLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { for (int i = 0; i < MAX_CHILDREN_COUNT; i++) { ImageView imageView = new ImageView(context); imageView.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); imageView.setBackgroundColor(Color.RED); addView(imageView); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); int notGoneChildCount = getNotGoneChildCount(); //不管什么模式,都是指定的固定寬高 itemWidth = (widthSize - horizontalSpacing * 2) / 3; itemHeight = itemWidth; //measureChildren內(nèi)部調(diào)用measureChild,這里我們就可以指定寬高 measureChildren(MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY)); if (heightMode == MeasureSpec.EXACTLY) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } else { notGoneChildCount = Math.min(notGoneChildCount, MAX_CHILDREN_COUNT); int heightSize = ((notGoneChildCount - 1) / 3 + 1) * (itemHeight + verticalSpacing) - verticalSpacing + getPaddingTop() + getPaddingBottom(); setMeasuredDimension(widthSize, heightSize); } } }
剛開(kāi)始的時(shí)候我們?cè)诓季殖跏蓟臅r(shí)候先添加5個(gè) mathc_parent 的9個(gè)子 View 作為測(cè)試。那么我們?cè)诓季值臅r(shí)候,就需要對(duì)寬度進(jìn)行分割,并且強(qiáng)制性的測(cè)量每一個(gè)子 View 的寬高為 EXACTLY 模式。
測(cè)量完每一個(gè)子 View 之后,我們?cè)賱?dòng)態(tài)的給 ViewGroup 設(shè)置寬高。
這樣測(cè)量之后的效果為:
為了方便查看效果,加上了測(cè)試的灰色背景,看著大小是符合預(yù)期的。接下來(lái)我們就開(kāi)始布局。
二、九宮格的布局
在之前流式布局的 onLayout 方法中,我們是通過(guò)動(dòng)態(tài)的拿到每一個(gè)子 View 的寬度去判斷當(dāng)前是否會(huì)超過(guò)總寬度,是否需要換行。
而這里我們就無(wú)需這么做了,因?yàn)槊恳粋€(gè)子 View 都是固定的寬度,一行就是三個(gè),一列最多也是三個(gè)。我們直接通過(guò)子 View 的數(shù)量就可以確定當(dāng)前的行數(shù)與列數(shù)。
然后我們就能行數(shù)和列數(shù)進(jìn)行布局了,具體的看代碼:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); int notGoneChildCount = getNotGoneChildCount(); int position = 0; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child.getVisibility() == View.GONE) { continue; } int row = position / 3; //當(dāng)前子View是第幾行(索引) int column = position % 3; //當(dāng)前子View是第幾列(索引) //當(dāng)前需要繪制的光標(biāo)的X與Y值 int x = column * itemWidth + getPaddingLeft() + horizontalSpacing * column; int y = row * itemHeight + getPaddingTop() + verticalSpacing * row; child.layout(x, y, x + itemWidth, y + itemHeight); //最多只擺放9個(gè) position++; if (position == MAX_CHILDREN_COUNT) { break; } } }
效果為:
如果對(duì)行和列的計(jì)算不清楚的,我們可以對(duì)每一個(gè)子 View 的位置進(jìn)行回顧,總共最多也就 9 個(gè),當(dāng)為第 0 個(gè)子 View 的時(shí)候,position為 0 ,那么 position / 3 是 0,row 就是 0, position % 3 也是 0,就是第最左上角的位置了。
當(dāng)為第1個(gè)子 View 的時(shí)候,position為1 ,那么 position / 3 還是0,row就是0, position % 3是1了,就是第一排中間的位置了。
只有當(dāng)View超過(guò)三個(gè)之后,position /3 就是 1 了,row為 1 之后,才是第二行的位置。依次類(lèi)推就可以定位到每一個(gè)子 View 需要繪制的位置。
而 x 與 y 的值與計(jì)算邏輯,我們可以想象為需要繪制當(dāng)前 View 的時(shí)候,當(dāng)前畫(huà)筆需要所在的位置。加上左右和上下的間距之后,我們通過(guò)這樣的方式也可以實(shí)現(xiàn) margin 的效果。還記得前文流式布局是怎么實(shí)現(xiàn) margin 效果的嗎?殊途同歸的效果。
最后具體的 child.layout 反而是最簡(jiǎn)單的,只需要繪制子 View 本身的寬高即可。
三、單圖片與四宮格的單獨(dú)處理
一般來(lái)說(shuō)我們需要單獨(dú)的處理一張圖片與四張圖片的邏輯。包括測(cè)量與布局都需要單獨(dú)的處理。
一張圖片的時(shí)候,我們需要通過(guò)方法單獨(dú)的指定圖片的寬度與高度。而四張圖片我們需要固定兩行的高度即可。
public class AbstractNineGridLayout extends ViewGroup { private static final int MAX_CHILDREN_COUNT = 9; //最大的子View數(shù)量 private int horizontalSpacing = 20; //每一個(gè)Item的左右間距 private int verticalSpacing = 20; //每一個(gè)Item的上下間距 private boolean fourGridMode = true; //是否支持四宮格模式 private boolean singleMode = true; //是否支持單布局模式 private boolean singleModeScale = true; //是否支持單布局模式按比例縮放 private int singleWidth; private int singleHeight; private int itemWidth; private int itemHeight; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); int notGoneChildCount = getNotGoneChildCount(); if (notGoneChildCount == 1 && singleMode) { itemWidth = singleWidth > 0 ? singleWidth : widthSize; itemHeight = singleHeight > 0 ? singleHeight : widthSize; if (itemWidth > widthSize && singleModeScale) { itemWidth = widthSize; //單張圖片先定寬度。 itemHeight = (int) (widthSize * 1f / singleWidth * singleHeight); //根據(jù)寬度計(jì)算高度 } } else { //除了單布局模式,其他的都是指定的固定寬高 itemWidth = (widthSize - horizontalSpacing * 2) / 3; itemHeight = itemWidth; } ... } /** * 設(shè)置單獨(dú)布局的寬和高 */ public void setSingleModeSize(int w, int h) { if (w != 0 && h != 0) { this.singleMode = true; this.singleWidth = w; this.singleHeight = h; } } }
測(cè)量的時(shí)候我們對(duì)單布局進(jìn)行測(cè)量,并且對(duì)超過(guò)寬度的一些布局做等比例的縮放。然后再測(cè)量父布局。
findViewById<AbstractNineGridLayout>(R.id.nine_grid).setSingleModeSize(dp2px(200f), dp2px(400f))
效果:
而如果是四宮格模式,我們好像也不需要重新測(cè)量,反正也是二行的高度,但是布局的時(shí)候我們需要處理一下,不然第三個(gè)子 View 的位置就會(huì)不對(duì)了。我們只需要修改x 與 y的計(jì)算方式,它們是根據(jù)行和列動(dòng)態(tài)計(jì)算你的,那么修改行和列的計(jì)算方式即可。
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); int notGoneChildCount = getNotGoneChildCount(); int position = 0; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child.getVisibility() == View.GONE) { continue; } int row = position / 3; //當(dāng)前子View是第幾行(索引) int column = position % 3; //當(dāng)前子View是第幾列(索引) if (notGoneChildCount == 4 && fourGridMode) { row = position / 2; column = position % 2; } //當(dāng)前需要繪制的光標(biāo)的X與Y值 int x = column * itemWidth + getPaddingLeft() + horizontalSpacing * column; int y = row * itemHeight + getPaddingTop() + verticalSpacing * row; child.layout(x, y, x + itemWidth, y + itemHeight); //最多只擺放9個(gè) position++; if (position == MAX_CHILDREN_COUNT) { break; } } } /** * 單獨(dú)設(shè)置是否支持四宮格模式 */ public void setFourGridMode(boolean enable) { this.fourGridMode = enable; }
這樣我們就可以支持四宮格的布局模式,效果如下:
到此,我們的九宮格控件大體上是完工了,但是還不夠靈活,內(nèi)部的子 View 都是我們自己 new 出來(lái)的,我們接下來(lái)就要暴露出去讓其可以自定義布局。
四、自定義布局的抽取
如何把填充布局的邏輯抽取出來(lái)呢?一般分為兩種思路:
- 每次初始化九宮格的時(shí)候就把九個(gè)布局全部添加進(jìn)來(lái),先測(cè)量布局了再說(shuō),然后通過(guò)暴露的方法隱藏多余的布局。
- 通過(guò)一個(gè)定義一個(gè)數(shù)據(jù)適配器Adapter,內(nèi)部封裝一些邏輯,讓具體實(shí)現(xiàn)的類(lèi)去完成具體的邏輯。
兩種方法都可以,沒(méi)有好壞之分。但是使用數(shù)據(jù)適配器的方案由于內(nèi)部的View會(huì)少,性能會(huì)好那么一丟丟,總體來(lái)說(shuō)差別不大。
4.1 先布局再隱藏的思路
一般我們?cè)诔橄蟮木艑m格類(lèi)中就需要暴露這兩個(gè)重要方法,一個(gè)是填充子布局的,一個(gè)是填充數(shù)據(jù)并且隱藏多余的布局。
//子類(lèi)去實(shí)現(xiàn)-填充布局文件 protected abstract void fillChildView(); //子類(lèi)去實(shí)現(xiàn)-對(duì)布局文件賦值數(shù)據(jù)(一般專(zhuān)門(mén)去給adapter去調(diào)用的) public abstract void renderData(T data);
例如我們的實(shí)現(xiàn)類(lèi):
@Override protected void fillChildView() { inflateChildLayout(R.layout.item_image_grid); imageViews = findInChildren(R.id.iv_image, ImageView.class); } @Override public void renderData(List<ImageInfo> imageInfos) { setSingleModeSize(imageInfos.get(0).getImageViewWidth(), imageInfos.get(0).getImageViewHeight()); setDisplayCount(imageInfos.size()); for (int i = 0; i < imageInfos.size(); i++) { String url = imageInfos.get(i).getThumbnailUrl(); ImageView imageView = imageViews[i]; //使用自定義的Loader加載 mImageLoader.onDisplayImage(getContext(), imageView, url); //點(diǎn)擊事件 setClickListener(imageView, i, imageInfos); } }
重點(diǎn)是填充的方法 inflateChildLayout 分為兩種情況,一種是布局都一樣的情況,一種是根據(jù)索引填充不同的布局情況。
/** * 可以為每一個(gè)子布局加載對(duì)應(yīng)的布局文件(不同的文件) */ protected void inflateChildLayoutCustom(ViewGetter viewGetter) { removeAllViews(); for (int i = 0; i < MAX_CHILDREN_COUNT; i++) { addView(viewGetter.getView(i)); } } /** * 一般用這個(gè)方法填充布局,每一個(gè)小布局的布局文件(相同的文件) */ protected void inflateChildLayout(int layoutId) { removeAllViews(); for (int i = 0; i < MAX_CHILDREN_COUNT; i++) { LayoutInflater.from(getContext()).inflate(layoutId, this); } }
而我們?cè)O(shè)置數(shù)據(jù)的方法中調(diào)用的 setDisplayCount 方法則是隱藏多余的控件的。
/** * 設(shè)置顯示的數(shù)量 */ public void setDisplayCount(int count) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { getChildAt(i).setVisibility(i < count ? VISIBLE : GONE); } }
效果:
4.2 數(shù)據(jù)適配器的思路
而使用數(shù)據(jù)適配器的方案,就無(wú)需每次上來(lái)就先填充9個(gè)子布局,而是通過(guò)Adapter動(dòng)態(tài)的配置當(dāng)前需要填充的數(shù)量,并且創(chuàng)建對(duì)應(yīng)的子 View 和綁定對(duì)應(yīng)的子 View 的數(shù)據(jù)。
聽(tīng)起來(lái)是不是很像RV的Apdater,沒(méi)錯(cuò)就是參考它的實(shí)現(xiàn)方式。
我們先創(chuàng)建一個(gè)基類(lèi)的Adapter:
public static abstract class Adapter { //返回總共子View的數(shù)量 public abstract int getItemCount(); //根據(jù)索引創(chuàng)建不同的布局類(lèi)型,如果都是一樣的布局則不需要重寫(xiě) public int getItemViewType(int position) { return 0; } //根據(jù)類(lèi)型創(chuàng)建對(duì)應(yīng)的View布局 public abstract View onCreateItemView(Context context, ViewGroup parent, int itemType); //可以根據(jù)類(lèi)型或索引綁定數(shù)據(jù) public abstract void onBindItemView(View itemView, int itemType, int position); }
然后我們需要暴露一個(gè)方法,設(shè)置Adapter,設(shè)置完成之后我們就可以添加對(duì)應(yīng)的布局了。
public void setAdapter(Adapter adapter) { mAdapter = adapter; inflateAllViews(); } private void inflateAllViews() { removeAllViewsInLayout(); if (mAdapter == null || mAdapter.getItemCount() == 0) { return; } int displayCount = Math.min(mAdapter.getItemCount(), MAX_CHILDREN_COUNT); //單布局處理 if (singleMode && displayCount == 1) { View view = mAdapter.onCreateItemView(getContext(), this, -1); addView(view); requestLayout(); return; } //多布局處理 for (int i = 0; i < displayCount; i++) { int itemType = mAdapter.getItemViewType(i); View view = mAdapter.onCreateItemView(getContext(), this, itemType); view.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); addView(view); } requestLayout(); }
需要注意的是我們?cè)贉y(cè)量的布局的時(shí)候,如果沒(méi)有 Adpter 或者沒(méi)有子布局的時(shí)候,我們需要單獨(dú)處理一下九宮格ViewGroup的高度。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); int notGoneChildCount = getNotGoneChildCount(); if (mAdapter == null || mAdapter.getItemCount() == 0 || notGoneChildCount == 0) { setMeasuredDimension(widthSize, 0); return; } ... }
那么如何綁定布局呢?在我們 onLayout完成之后我們就可以綁定數(shù)據(jù)了。
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { ... performBind(); } /** * 布局完成之后綁定對(duì)應(yīng)的數(shù)據(jù)到對(duì)應(yīng)的ItemView */ private void performBind() { if (mAdapter == null || mAdapter.getItemCount() == 0) { return; } post(() -> { for (int i = 0; i < getNotGoneChildCount(); i++) { int itemType = mAdapter.getItemViewType(i); View view = getChildAt(i); mAdapter.onBindItemView(view, itemType, i); } }); }
具體的實(shí)現(xiàn)就是在 Adapter 中實(shí)現(xiàn)了。
例如我們創(chuàng)建一個(gè)最簡(jiǎn)單的圖片九宮格適配器。
public class ImageNineGridAdapter extends AbstractNineGridLayout.Adapter { private List<String> mDatas = new ArrayList<>(); public ImageNineGridAdapter(List<String> data) { mDatas.addAll(data); } @Override public int getItemCount() { return mDatas.size(); } @Override public View onCreateItemView(Context context, ViewGroup parent, int itemType) { return LayoutInflater.from(context).inflate(R.layout.item_img, parent, false); } @Override public void onBindItemView(View itemView, int itemType, int position) { itemView.findViewById(R.id.iv_img).setBackgroundColor(Color.RED); } }
在Activity中設(shè)置對(duì)應(yīng)的數(shù)據(jù)適配器:
findViewById<AbstractNineGridLayout>(R.id.nine_grid).run { setSingleModeSize(dp2px(200f), dp2px(400f)) setAdapter(ImageNineGridAdapter(imgs)) }
我們就能得到同樣的效果:
如果想九宮格內(nèi)使用不同的布局,不同的索引展示不同的邏輯,都可以很方便的實(shí)現(xiàn):
public class ImageNineGridAdapter extends AbstractNineGridLayout.Adapter { private List<String> mDatas = new ArrayList<>(); public ImageNineGridAdapter(List<String> data) { mDatas.addAll(data); } @Override public int getItemViewType(int position) { if (position == 1) { return 10; } else { return 0; } } @Override public int getItemCount() { return mDatas.size(); } @Override public View onCreateItemView(Context context, ViewGroup parent, int itemType) { if (itemType == 0) { return LayoutInflater.from(context).inflate(R.layout.item_img, parent, false); } else { return LayoutInflater.from(context).inflate(R.layout.item_img_icon, parent, false); } } @Override public void onBindItemView(View itemView, int itemType, int position) { if (itemType == 0) { itemView.findViewById(R.id.iv_img).setBackgroundColor(position == 0 ? Color.RED : Color.YELLOW); } } }
效果:
到這里我們的控件就基本上能實(shí)現(xiàn)大部分業(yè)務(wù)需求了,接下來(lái)我會(huì)對(duì)一些屬性與配置進(jìn)行抽取,并開(kāi)源上傳到云端。
到此這篇關(guān)于Android自定義ViewGroup實(shí)現(xiàn)九宮格布局的文章就介紹到這了,更多相關(guān)Android ViewGroup九宮格布局內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android實(shí)現(xiàn)滑動(dòng)標(biāo)簽頁(yè)效果的代碼解析
這篇文章主要介紹了android實(shí)現(xiàn)滑動(dòng)標(biāo)簽頁(yè)效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04Kotlin Service實(shí)現(xiàn)消息推送通知過(guò)程
這幾天分析了一下的啟動(dòng)過(guò)程,于是乎,今天寫(xiě)一下Service使用; 給我的感覺(jué)是它并不復(fù)雜,千萬(wàn)不要被一坨一坨的代碼嚇住了,雖然彎彎繞繞不少,重載函數(shù)一個(gè)接著一個(gè),就向走迷宮一樣,但只要抓住主線(xiàn)閱讀,很快就能找到出口2022-12-12代碼從windows下visual studio到andriod平臺(tái)遷移實(shí)現(xiàn)步驟
這篇文章主要介紹了代碼從windows下visual studio到andriod平臺(tái)遷移的修改記錄的相關(guān)資料,需要的朋友可以參考下2017-01-01Android自定義水平進(jìn)度條的圓角進(jìn)度
這篇文章主要為大家詳細(xì)介紹了Android自定義水平進(jìn)度條的圓角進(jìn)度,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08Android實(shí)現(xiàn)知乎選項(xiàng)卡動(dòng)態(tài)隱藏效果實(shí)例
選項(xiàng)卡相信對(duì)大家來(lái)說(shuō)應(yīng)該不陌生,最近發(fā)現(xiàn)知乎選項(xiàng)卡的動(dòng)態(tài)隱藏效果不錯(cuò),下面這篇文章主要給大家介紹了關(guān)于Android實(shí)現(xiàn)知乎選項(xiàng)卡動(dòng)態(tài)隱藏效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02Flutter+Metal實(shí)現(xiàn)圖像處理詳細(xì)流程
Flutter使用CVPixelBuffer和iOS交互,我們可以直接使用CVPixelBuffer創(chuàng)建MTLTexture,然后將MTLTexture設(shè)置為渲染目標(biāo),這篇文章主要介紹了Flutter+Metal實(shí)現(xiàn)圖像處理,需要的朋友可以參考下2022-06-06Android ANR無(wú)響應(yīng)分析解決方案
這篇文章主要為大家介紹了Android ANR無(wú)響應(yīng)分析解決方案,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Android仿QQ好友列表分組實(shí)現(xiàn)增刪改及持久化
這篇文章主要介紹了Android仿QQ好友列表分組實(shí)現(xiàn)增刪改及持久化的相關(guān)資料,需要的朋友可以參考下2016-01-01android藍(lán)牙簡(jiǎn)單開(kāi)發(fā)示例教程
大家好,本篇文章主要講的是android藍(lán)牙簡(jiǎn)單開(kāi)發(fā)示例教程,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下2021-12-12Android 使用AsyncTask實(shí)現(xiàn)多線(xiàn)程斷點(diǎn)續(xù)傳
本文將詳細(xì)講解如何使用AsyncTask來(lái)實(shí)現(xiàn)多線(xiàn)程的斷點(diǎn)續(xù)傳下載功能,感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧2018-05-05