Android ItemDecoration 實(shí)現(xiàn)分組索引列表的示例代碼
本文介紹了Android ItemDecoration 實(shí)現(xiàn)分組索引列表的示例代碼,分享給大家。具體如下:
先來(lái)看看效果:


我們要實(shí)現(xiàn)的效果主要涉及三個(gè)部分:
- 分組 GroupHeader
- 分割線
- SideBar
前兩個(gè)部分涉及到一個(gè)ItemDecoration類,也是我們接下來(lái)的重點(diǎn),該類是RecyclerView的一個(gè)抽象靜態(tài)內(nèi)部類,主要作用就是給RecyclerView的ItemView繪制額外的裝飾效果,例如給RecyclerView添加分割線。
使用ItemDecoration時(shí)需要繼承該類,根據(jù)需求可以重寫如下三個(gè)方法,其它的方法已經(jīng)deprecated了:
public class GroupHeaderItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
}
然后將其添加到RecyclerView中:
recyclerView.addItemDecoration(new GroupHeaderItemDecoration())
了解這個(gè)三個(gè)方法的作用,這樣才能更好的實(shí)現(xiàn)我們想要的功能:
1、getItemOffsets()
給指定的ItemView設(shè)置偏移量,具體怎么設(shè)置呢,咱們看圖說(shuō)話:

圖中左邊的是原始RecyclerView列表,右邊是設(shè)置了ItemView偏移量的列表,其實(shí)相當(dāng)于在ItemView外部添加了一個(gè)矩形區(qū)域
其中l(wèi)eft、top、right、bottom就是ItemView在四個(gè)方向的偏移量,對(duì)應(yīng)的設(shè)置代碼如下:
outRect.set(left, top, right, bottom)
在我們的分組索引列表中,只需要對(duì)ItemView設(shè)置頂部的偏移量,其它三個(gè)偏移量為0即可。這樣就可以在ItemView頂部預(yù)留出一定高度的區(qū)域,如下圖:

2、onDraw()
在getItemOffsets()方法中,我們?cè)O(shè)置了偏移量,進(jìn)而得到了對(duì)應(yīng)的偏移區(qū)域,接下來(lái)在onDraw()中就可以給ItemView繪制裝飾效果了,所以我們?cè)谠摲椒ㄖ袑⒎纸M索引列表中的GroupHeader的內(nèi)容繪制在ItemView頂部偏移區(qū)域里。也就是繪制前邊 gif 圖里的A、B、C... GroupHeader,雖然看起來(lái)像一個(gè)個(gè)獨(dú)立的ItemView,但并不是的哦!
注意該繪制操作會(huì)在ItemView的onDraw()前完成的!
3、onDrawOver()
該方法同樣也是用來(lái)繪制的,但是它在ItemDecoration的onDraw()方法和ItemView的onDraw()完成后才執(zhí)行。所以其繪制的內(nèi)容會(huì)遮擋在RecyclerView上,因此我們可以在該方法中繪制分組索引列表中懸浮的GroupHeader,也就是在列表頂部隨著列表滾動(dòng)切換的GroupHeader。
一、分組GroupHeader
三個(gè)方法的作用已經(jīng)解釋完了,接下來(lái)就是代碼實(shí)現(xiàn)我們的效果了:
首先保證RecyclerView的數(shù)據(jù)源已經(jīng)按照某種規(guī)律進(jìn)行了分組排序,具體什么規(guī)律你說(shuō)了算,我們例子中按照數(shù)據(jù)源中指定字段的值的首字母升序排列,也就是常見通訊錄的排序方式。然后在每個(gè)data中保存需要在GroupHeader上顯示的內(nèi)容,可以使用tag字段,我們這里保存的是對(duì)應(yīng)的首字母。這里沒必要將整個(gè)數(shù)據(jù)源設(shè)置到ItemDecoration里邊,所以我們只需要提取排序后數(shù)據(jù)源的tag保存到列表中,然后設(shè)置到ItemDecoration里邊,后邊的操作就依賴設(shè)置的數(shù)據(jù)源了,根據(jù)tag的異同來(lái)決定是否繪制GroupHeader等。
上邊已經(jīng)分析了,GroupHeader只在列表中每組數(shù)據(jù)對(duì)應(yīng)的第一個(gè)ItemView頂部顯示,只需要對(duì)ItemView設(shè)置頂部的偏移量即可:
public class GroupHeaderItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
RecyclerView.LayoutManager manager = parent.getLayoutManager();
//只處理線性垂直類型的列表
if ((manager instanceof LinearLayoutManager)
&& LinearLayoutManager.VERTICAL != ((LinearLayoutManager) manager).getOrientation()) {
return;
}
int position = parent.getChildAdapterPosition(view);
//ItemView的position==0 或者 當(dāng)前ItemView的data的tag和上一個(gè)ItemView的不相等,則為當(dāng)前ItemView設(shè)置top 偏移量
if (!Utils.listIsEmpty(tags) && (position == 0 || !tags.get(position).equals(tags.get(position - 1)))) {
outRect.set(0, groupHeaderHeight, 0, 0);
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
}
其中tags就是我們?cè)O(shè)置到ItemDecoration的數(shù)據(jù)源,是一個(gè)String集合。groupHeaderHeight就是ItemView的頂部偏移量。
之后就是在ItemView的頂部偏移區(qū)域繪制GroupHeader了:
public class GroupHeaderItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
for (int i = 0; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
String tag = tags.get(position);
//和getItemOffsets()里的條件判斷類似,開始繪制分組的GroupHeader
if (!Utils.listIsEmpty(tags) && (position == 0 || !tag.equals(tags.get(position - 1)))) {
drawGroupHeader(c, parent, view, tag);
}
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
private void drawGroupHeader(Canvas c, RecyclerView parent, View view, String tag) {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int bottom = view.getTop() - params.topMargin;
int top = bottom - groupHeaderHeight;
c.drawRect(left, top, right, bottom, mPaint);
int x = left + groupHeaderLeftPadding;
int y = top + (groupHeaderHeight + Utils.getTextHeight(mTextPaint, tag)) / 2;
c.drawText(tag, x, y, mTextPaint);
}
}
繪制GroupHeader就是Canvasc操作,先繪制一個(gè)矩形框,再繪制相應(yīng)的文字,當(dāng)然繪制圖片也是沒問題的,其中g(shù)roupHeaderLeftPadding是個(gè)可配置字段,代表繪制的文字或圖片到列表左邊沿的距離,也可以理解為GroupHeader的左padding。
最后就是懸浮在頂部的GroupHeader繪制了:
public class GroupHeaderItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if (!show) {
return;
}
//列表第一個(gè)可見的ItemView位置
int position = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
String tag = tags.get(position);
View view = parent.findViewHolderForAdapterPosition(position).itemView;
//當(dāng)前ItemView的data的tag和下一個(gè)itemView的不相等,則代表將要重新繪制懸停的GroupHeader
boolean flag = false;
if (!Utils.listIsEmpty(tags) && (position + 1) < tags.size() && !tag.equals(tags.get(position + 1))) {
//如果第一個(gè)可見ItemView的底部坐標(biāo)小于groupHeaderHeight,則執(zhí)行Canvas向上位移操作
if (view.getBottom() <= groupHeaderHeight) {
c.save();
flag = true;
c.translate(0, view.getHeight() + view.getTop() - groupHeaderHeight);
}
}
drawSuspensionGroupHeader(c, parent, tag);
if (flag) {
c.restore();
}
}
private void drawSuspensionGroupHeader(Canvas c, RecyclerView parent, String tag) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int bottom = groupHeaderHeight;
int top = 0;
c.drawRect(left, top, right, bottom, mPaint);
int x = left + groupHeaderLeftPadding;
int y = top + (groupHeaderHeight + Utils.getTextHeight(mTextPaint, tag)) / 2;
c.drawText(tag, x, y, mTextPaint);
}
}
繪制操作和onDraw中的類似,gif 中有一個(gè)懸浮GroupHeader上移的動(dòng)畫,就是通過Canvas位移來(lái)實(shí)現(xiàn)的,注意在Canvas位移的前后進(jìn)行save()和restore()操作。
我們給GroupHeaderItemDecoration提供了設(shè)置GroupHeader左padding、高度、背景色、文字顏色、尺寸、以及是否顯示頂部懸浮GroupHeader的方法,方便使用。
關(guān)于繪制操作需要注意的是,GroupHeader所在的偏移區(qū)域和ItemView是相互獨(dú)立的,不要把GroupHeader當(dāng)做ItemView的一部分哦。到這里GroupHeader的功能就實(shí)現(xiàn)了,只需要將GroupHeaderItemDecoration添加到RecyclerView即可。
至于如何通過layout或者View來(lái)實(shí)現(xiàn)GroupHeader,做過一些嘗試,效果都不理想,期待大家的好想法哦!
這里先用一個(gè)接口,對(duì)外提供自定義繪制GroupHeader的方法:
public interface OnDrawItemDecorationListener {
/**
* 繪制GroupHeader
* @param c
* @param paint 繪制GroupHeader區(qū)域的paint
* @param textPaint 繪制文字的paint
* @param params 共四個(gè)值left、top、right、bottom 代表GroupHeader所在區(qū)域的四個(gè)坐標(biāo)值
* @param position 原始數(shù)據(jù)源中的position
*/
void onDrawGroupHeader(Canvas c, Paint paint, TextPaint textPaint, int[] params, int position);
/**
* 繪制懸浮在列表頂部的GroupHeader
*/
void onDrawSuspensionGroupHeader(Canvas c, Paint paint, TextPaint textPaint, int[] params, int position);
}
二、分割線
現(xiàn)在RecyclerView還差一個(gè)分割線,當(dāng)前最笨的辦法可以在ItemView的布局文件中設(shè)置,既然系統(tǒng)都提供了ItemDecoration,那用它來(lái)優(yōu)雅的實(shí)現(xiàn)為何不可呢,我們只需要給列表中每組數(shù)據(jù)除了最后一項(xiàng)數(shù)據(jù)對(duì)應(yīng)的ItemView之外的添加分割線即可,也就是不給每組數(shù)據(jù)對(duì)應(yīng)的最后一個(gè)ItemView添加分割線。很簡(jiǎn)單,直接上核心代碼:
public class DivideItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
RecyclerView.LayoutManager manager = parent.getLayoutManager();
//只處理線性垂直類型的列表
if ((manager instanceof LinearLayoutManager)
&& LinearLayoutManager.VERTICAL != ((LinearLayoutManager) manager).getOrientation()) {
return;
}
int position = parent.getChildAdapterPosition(view);
if (!Utils.listIsEmpty(tags) && (position + 1) < tags.size() && tags.get(position).equals(tags.get(position + 1))) {
//當(dāng)前ItemView的data的tag和下一個(gè)ItemView的不相等,則為當(dāng)前ItemView設(shè)置bottom 偏移量
outRect.set(0, 0, 0, divideHeight);
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
for (int i = 0; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
//和getItemOffsets()里的條件判斷類似
if (!Utils.listIsEmpty(tags) && (position + 1) < tags.size() && tags.get(position).equals(tags.get(position + 1))) {
drawDivide(c, parent, view);
}
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
private void drawDivide(Canvas c, RecyclerView parent, View view) {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
int left = parent.getPaddingLeft();
int right = parent.getWidth();
int top = view.getBottom() + params.bottomMargin;
int bottom = top + divideHeight;
c.drawRect(left, top, right, bottom, mPaint);
}
}
三、SideBar
SideBar就是 gif 圖右邊的垂直字符條,是一個(gè)自定義View。手指觸摸選中一個(gè)字符,則列表會(huì)滾動(dòng)到對(duì)應(yīng)的分組頭部位置。實(shí)現(xiàn)起來(lái)也蠻簡(jiǎn)單的,核心代碼如下:
public class SideBar extends View {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//重新計(jì)算SideBar寬高
if (heightMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.AT_MOST) {
getMaxTextSize();
if (heightMode == MeasureSpec.AT_MOST) {
heightSize = (maxHeight + 15) * indexArray.length;
}
if (widthMode == MeasureSpec.AT_MOST) {
widthSize = maxWidth + 10;
}
}
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < indexArray.length; i++) {
String index = indexArray[i];
float x = (mWidth - mTextPaint.measureText(index)) / 2;
float y = mMarginTop + mHeight * i + (mHeight + Utils.getTextHeight(mTextPaint, index)) / 2;
//繪制字符
canvas.drawText(index, x, y, mTextPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// 選中字符的下標(biāo)
int pos = (int) ((event.getY() - mMarginTop) / mHeight);
if (pos >= 0 && pos < indexArray.length) {
setBackgroundColor(TOUCH_COLOR);
if (onSideBarTouchListener != null) {
for (int i = 0; i < tags.size(); i++) {
if (indexArray[pos].equals(tags.get(i))) {
onSideBarTouchListener.onTouch(indexArray[pos], i);
break;
} else {
onSideBarTouchListener.onTouch(indexArray[pos], -1);
}
}
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
setBackgroundColor(UNTOUCH_COLOR);
if (onSideBarTouchListener != null) {
onSideBarTouchListener.onTouchEnd();
}
break;
}
return true;
}
}
在onMeasure()方法里,如果SideBar的寬、高測(cè)量模式為MeasureSpec.AT_MOST則重新計(jì)算SideBar的寬、高。onDraw()方法則是遍歷索引數(shù)組,并繪制字符索引。在onTouchEvent()方法里,我們根據(jù)手指在SideBar上觸摸坐標(biāo)點(diǎn)的y值,計(jì)算出觸摸的相應(yīng)字符,以便在OnSideBarTouchListener接口進(jìn)行后續(xù)操作,例如列表的跟隨滾動(dòng)等等。
四、實(shí)例
前邊已經(jīng)完成了三大核心功能,最后來(lái)愉快的使用下吧:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
SideBar sideBar = (SideBar) findViewById(R.id.side_bar);
final TextView tip = (TextView) findViewById(R.id.tip);
final List<ItemData> datas = new ArrayList<>();
ItemData data = new ItemData("北京");
datas.add(data);
ItemData data1 = new ItemData("上海");
datas.add(data1);
ItemData data2 = new ItemData("廣州");
datas.add(data2);
.
.
.
ItemData data34 = new ItemData("Hello China");
datas.add(data34);
ItemData data35 = new ItemData("寧波");
datas.add(data35);
SortHelper<ItemData> sortHelper = new SortHelper<ItemData>() {
@Override
public String sortField(ItemData data) {
return data.getTitle();
}
};
sortHelper.sortByLetter(datas);//將數(shù)據(jù)源按指定字段首字母排序
List<String> tags = sortHelper.getTags(datas);//提取已排序數(shù)據(jù)源的tag值
MyAdapter adapter = new MyAdapter(this, datas, false);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
//添加分割線
recyclerView.addItemDecoration(new DivideItemDecoration().setTags(tags));
//添加GroupHeader
recyclerView.addItemDecoration(new GroupHeaderItemDecoration(this)
.setTags(tags)//設(shè)置tag集合
.setGroupHeaderHeight(30)//設(shè)置GroupHeader高度
.setGroupHeaderLeftPadding(20));//設(shè)置GroupHeader 左padding
recyclerView.setAdapter(adapter);
sideBar.setOnSideBarTouchListener(tags, new OnSideBarTouchListener() {
@Override
public void onTouch(String text, int position) {
tip.setVisibility(View.VISIBLE);
tip.setText(text);
if ("↑".equals(text)) {
layoutManager.scrollToPositionWithOffset(0, 0);
return;
}
//滾動(dòng)列表到指定位置
if (position != -1) {
layoutManager.scrollToPositionWithOffset(position, 0);
}
}
@Override
public void onTouchEnd() {
tip.setVisibility(View.GONE);
}
});
}
}
這也就是文章開頭的 gif 效果。如果需要自定義ItemView的繪制可以這樣寫:
recyclerView.addItemDecoration(new GroupHeaderItemDecoration(this)
.setTags(tags)
.setGroupHeaderHeight(30)
.setGroupHeaderLeftPadding(20)
.setOnDrawItemDecorationListener(new OnDrawItemDecorationListener() {
@Override
public void onDrawGroupHeader(Canvas c, Paint paint, TextPaint textPaint, int[] params, int position) {
c.drawRect(params[0], params[1], params[2], params[3], paint);
int x = params[0] + Utils.dip2px(context, 20);
int y = params[1] + (Utils.dip2px(context, 30) + Utils.getTextHeight(textPaint, tags.get(position))) / 2;
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher, null);
Bitmap icon1 = Bitmap.createScaledBitmap(icon, Utils.dip2px(context, 20), Utils.dip2px(context, 20), true);
c.drawBitmap(icon1, x, params[1] + Utils.dip2px(context, 5), paint);
c.drawText(tags.get(position), x + Utils.dip2px(context, 25), y, textPaint);
}
@Override
public void onDrawSuspensionGroupHeader(Canvas c, Paint paint, TextPaint textPaint, int[] params, int position) {
c.drawRect(params[0], params[1], params[2], params[3], paint);
int x = params[0] + Utils.dip2px(context, 20);
int y = params[1] + (Utils.dip2px(context, 30) + Utils.getTextHeight(textPaint, tags.get(position))) / 2;
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher, null);
Bitmap icon1 = Bitmap.createScaledBitmap(icon, Utils.dip2px(context, 20), Utils.dip2px(context, 20), true);
c.drawBitmap(icon1, x, params[1] + Utils.dip2px(context, 5), paint);
c.drawText(tags.get(position), x + Utils.dip2px(context, 25), y, textPaint);
}
})
);
坐標(biāo)計(jì)算有點(diǎn)復(fù)雜了......0_o......
看下效果:

當(dāng)然不止于此,更多的效果等待著機(jī)智的你去創(chuàng)造。
更多代碼細(xì)節(jié)及用法可參考:https://github.com/Othershe/GroupIndexLib
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實(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)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02
Android實(shí)現(xiàn)復(fù)制Assets文件到SD卡
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)復(fù)制Assets文件到SD卡,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
Android Studio搜索功能(查找功能)及快捷鍵圖文詳解
這篇文章主要介紹了Android Studio搜索功能(查找功能)及快捷鍵圖文詳解,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下2017-12-12
Android自定義商品購(gòu)買數(shù)量加減控件
這篇文章主要為大家詳細(xì)介紹了Android自定義商品購(gòu)買數(shù)量加減控件的相關(guān)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Android Dialog對(duì)話框用法實(shí)例詳解
這篇文章主要介紹了Android Dialog對(duì)話框用法,結(jié)合實(shí)例形式分析了Android使用Dialog對(duì)話框過程中所涉及的創(chuàng)建、保存、回復(fù)等操作相關(guān)技巧與注意事項(xiàng),需要的朋友可以參考下2016-07-07
Android中SeekBar拖動(dòng)條使用方法詳解
這篇文章主要為大家詳細(xì)介紹了Android中SeekBar拖動(dòng)條使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03
Android跳轉(zhuǎn)三方應(yīng)用實(shí)例代碼
大家好,本篇文章主要講的是Android跳轉(zhuǎn)三方應(yīng)用實(shí)例代碼,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12

