摘要:需求分析在餓了么首頁中我們能看到這樣的布局如下圖。導航點是使用文件畫的,灰色為未選中,綠色為選中。因為默認顯示的是第一頁,所以我們默認第一個的導航點是選中的。
1、需求分析
在餓了么首頁中我們能看到這樣的布局(如下圖)。紅框內是一個可以左右滑動的頁面,每一個頁面類似于九宮格,都有可供點擊圖標。對于這樣的布局,我在網上找了很久都沒有找到相關的名稱,所以我這里暫時叫它導航頁吧。
最近公司的項目就要求我實現一個這樣的布局,但是我們的圖標并不是想餓了么這樣是固定的,所以在餓了么的布局上還要加一個效果:在圖標數目無法排滿兩行時,就只顯示一行。比如說,我們每一頁最多可以顯示兩行和四列,當圖標的總數目小于或等于4個時就只顯示出一行,第二行就不要了。這樣頁面就不至于留出太多的空白。
先梳理一下我們要實現的效果:
大體的框架是一個可以左右滑動的頁面;
每一頁為兩行四列的矩陣;
底部有和頁面數目相等的導航點,且滑動到某個頁面時,相應的導航點會改變顏色;
單選事件:選中圖標后,圖標變為完全不透明。
明白需求之后,我們穿越時空看看最后實現后的布局:
圖片資源大家可以在我源碼中找到。
2、實現思路我們已經分析了需求,現在就來分析一下怎么實現。
必須事先說明一下,我的實現方法比較暴力,也比較占資源,所以大家要是有更好的方法的話歡迎留言告訴我,我們互相學習。
首先,這是一個左右滑動的頁面,所以我們可以考慮使用ViewPager我們每個頁面有8個圖標,也就是8個item。在圖標的數目在4以下時就只顯示第一行,所以布局上我們可以將四個item作為一行放置到橫向的線性布局中,當第二行沒有圖標時就讓它消失。
ViewPager中的每一頁的布局都是一樣,所以我們可以對其復用:將圖標分組,每8個為一組(余下的不到八頁也歸為一組),每一組即為一頁。每次滑動的頁面時就加載布局,填充圖標和文字。
下面的導航點就比較簡單了,可以使用ViewPager的滑動監聽事件進行監聽,滑動到選中的頁面時就改變導航點的顏色。
至于透明,我們只需要記錄圖標的選中狀態,然后改變透明度即可。
3、編寫布局接下來就讓我們創建項目,編寫頁面布局吧。
在Android Studio中創建一個NavigationPager項目,MainActivity的布局如下:
activity_main.xmlMainActivity的主布局是一個縱向的線性布局,上面一個ViewPager,下面一個橫向的線性布局,用于放置導航點。這里得說一下ViewPager的特性,在不指定特定高度的情況下,ViewPager的高度是默認填充整個父布局的,我們顯然不希望這樣,畢竟我們還要分一行和兩行兩種情況呢。所以這里我在這里使用了一個可以自適應高度的自定義ViewPager:
CustomViewPager/** * Created by Lindroid on 2017/3/20. * 自適應高度的ViewPager */ public class CustomViewPager extends ViewPager { public CustomViewPager(Context context) { super(context); } public CustomViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int height = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); int h = child.getMeasuredHeight(); if (h > height) height = h; } heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }
頁面的布局(也就是ViewPager的子布局)可以拆分成兩個橫向排列的布局:
layout_line1.xml和layout_line2.xml分別為第一行和第二行的布局,下面是layout_line1.xml的布局。layout_line2.xml除了控件的id名不同之外,其他的都一樣,這里就不貼出來了。
好了,我們所需要的布局都已經編寫完畢,下面就正式開始寫代碼了!
4、編寫代碼 4.1 創建Bean類創建一個NavigationItemBean類,用于存儲圖標的圖片id、名稱和選中狀態等信息。
public class NavigationItemBean { private int iconID; private String iconName; private boolean isSelected; public NavigationItemBean(int iconID, String iconName) { this.iconID = iconID; this.iconName = iconName; } public boolean isSelected() { return isSelected; } public void setSelected(boolean selected) { isSelected = selected; } public int getIconID() { return iconID; } public void setIconID(int iconID) { this.iconID = iconID; } public String getIconName() { return iconName; } public void setIconName(String iconName) { this.iconName = iconName; } }4.2 設置圖標資源
為了提高效率,我在初始化控件時使用了ButterKnife,大家需要它的jar包的話也可以從我的工程中復制粘貼。
public class MainActivity extends AppCompatActivity { @Bind(R.id.vp_navigation) CustomViewPager vpNavigation; @Bind(R.id.ll_dots) LinearLayout llDots; private NavigationAdapter adapter; private ListitemBeanList = new ArrayList<>(); private List dots = new ArrayList<>(); //圖標id數組 private int[] icons = {R.mipmap.phone, R.mipmap.browser, R.mipmap.messages, R.mipmap.contacts, R.mipmap.camera, R.mipmap.gallery, R.mipmap.calendar, R.mipmap.calculator, R.mipmap.settings, R.mipmap.mail, R.mipmap.maps, R.mipmap.music, R.mipmap.movie}; //圖標名稱數組 private String[] names = {"電話", "瀏覽器", "信息", "聯系人", "照相", "圖庫", "日歷", "計算器", "設置", "郵箱", "地圖", "音樂", "電影"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); initData(); } private void initData() { for (int i = 0; i < icons.length; i++) { itemBeanList.add(new NavigationItemBean(icons[i], names[i])); } adapter = new NavigationAdapter(this, itemBeanList, this); vpNavigation.setAdapter(adapter); } }
先將圖標的id和名稱存儲到兩個數組中,再遍歷數組將資源添加到集合中。
4.3 創建適配器這一步應該是整個項目中最難的一步了,所以要打起精神來了。先看下面的代碼:
public class NavigationAdapter extends PagerAdapter { @Bind(R.id.iv_icon1) ImageView ivIcon1; @Bind(R.id.tv_name1) TextView tvName1; @Bind(R.id.ll_item1) LinearLayout llItem1; @Bind(R.id.iv_icon2) ImageView ivIcon2; @Bind(R.id.tv_name2) TextView tvName2; @Bind(R.id.ll_item2) LinearLayout llItem2; @Bind(R.id.iv_icon3) ImageView ivIcon3; @Bind(R.id.tv_name3) TextView tvName3; @Bind(R.id.ll_item3) LinearLayout llItem3; @Bind(R.id.iv_icon4) ImageView ivIcon4; @Bind(R.id.tv_name4) TextView tvName4; @Bind(R.id.ll_item4) LinearLayout llItem4; @Bind(R.id.iv_icon5) ImageView ivIcon5; @Bind(R.id.tv_name5) TextView tvName5; @Bind(R.id.ll_item5) LinearLayout llItem5; @Bind(R.id.iv_icon6) ImageView ivIcon6; @Bind(R.id.tv_name6) TextView tvName6; @Bind(R.id.ll_item6) LinearLayout llItem6; @Bind(R.id.iv_icon7) ImageView ivIcon7; @Bind(R.id.tv_name7) TextView tvName7; @Bind(R.id.ll_item7) LinearLayout llItem7; @Bind(R.id.iv_icon8) ImageView ivIcon8; @Bind(R.id.tv_name8) TextView tvName8; @Bind(R.id.ll_item8) LinearLayout llItem8; @Bind(R.id.ll_line2) LinearLayout llLine2; private Context context; private ListitemBeanList; private List itemViews; int pages; //總頁數 private View pageView; private View.OnClickListener onClickListener; public NavigationAdapter(Context context, List itemBeanList, View.OnClickListener onClickListener) { this.context = context; this.itemBeanList = itemBeanList; this.onClickListener = onClickListener; itemViews = new ArrayList<>(); initView(itemBeanList); } /** * 初始化ViewPager的頁面布局 * @param list */ private void initView(List list) { itemViews.clear(); //計算ViewPager的頁數 pages = list.size() / 9; pages = pages + 1; for (int i = 0; i < pages; i++) { pageView = View.inflate(context, R.layout.page_navigation, null); ButterKnife.bind(this, pageView); setPagerViewData(i); itemViews.add(pageView); } } /** * 根據頁碼來填充數據 * @param pageNum */ public void setPagerViewData(int pageNum) { if (itemBeanList.size() > pageNum * 8 + 0) { llItem1.setVisibility(View.VISIBLE); tvName1.setText(itemBeanList.get(pageNum * 8 + 0).getIconName()); ivIcon1.setBackgroundResource(itemBeanList.get(pageNum * 8 + 0).getIconID()); setIconAlpha(itemBeanList.get(pageNum * 8 + 0).isSelected(), ivIcon1); } else { llItem1.setVisibility(View.INVISIBLE); } if (itemBeanList.size() > pageNum * 8 + 1) { llItem2.setVisibility(View.VISIBLE); tvName2.setText(itemBeanList.get(pageNum * 8 + 1).getIconName()); ivIcon2.setBackgroundResource(itemBeanList.get(pageNum * 8 + 1).getIconID()); setIconAlpha(itemBeanList.get(pageNum * 8 + 1).isSelected(), ivIcon2); } else { llItem2.setVisibility(View.INVISIBLE); } if (itemBeanList.size() > pageNum * 8 + 2) { llItem3.setVisibility(View.VISIBLE); tvName3.setText(itemBeanList.get(pageNum * 8 + 2).getIconName()); ivIcon3.setBackgroundResource(itemBeanList.get(pageNum * 8 + 2).getIconID()); setIconAlpha(itemBeanList.get(pageNum * 8 + 2).isSelected(), ivIcon3); } else { llItem3.setVisibility(View.INVISIBLE); } if (itemBeanList.size() > pageNum * 8 + 3) { llItem4.setVisibility(View.VISIBLE); tvName4.setText(itemBeanList.get(pageNum * 8 + 3).getIconName()); ivIcon4.setBackgroundResource(itemBeanList.get(pageNum * 8 + 3).getIconID()); setIconAlpha(itemBeanList.get(pageNum * 8 + 3).isSelected(), ivIcon4); } else { llItem4.setVisibility(View.INVISIBLE); } if (itemBeanList.size() > pageNum * 8 + 4) { llItem5.setVisibility(View.VISIBLE); tvName5.setText(itemBeanList.get(pageNum * 8 + 4).getIconName()); ivIcon5.setBackgroundResource(itemBeanList.get(pageNum * 8 + 4).getIconID()); setIconAlpha(itemBeanList.get(pageNum * 8 + 4).isSelected(), ivIcon5); } else { llItem5.setVisibility(View.INVISIBLE); llLine2.setVisibility(View.GONE); } if (itemBeanList.size() > pageNum * 8 + 5) { llItem6.setVisibility(View.VISIBLE); tvName6.setText(itemBeanList.get(pageNum * 8 + 5).getIconName()); ivIcon6.setBackgroundResource(itemBeanList.get(pageNum * 8 + 5).getIconID()); setIconAlpha(itemBeanList.get(pageNum * 8 + 5).isSelected(), ivIcon6); } else { llItem6.setVisibility(View.INVISIBLE); } if (itemBeanList.size() > pageNum * 8 + 6) { llItem7.setVisibility(View.VISIBLE); tvName7.setText(itemBeanList.get(pageNum * 8 + 6).getIconName()); ivIcon7.setBackgroundResource(itemBeanList.get(pageNum * 8 + 6).getIconID()); setIconAlpha(itemBeanList.get(pageNum * 8 + 6).isSelected(), ivIcon7); } else { llItem7.setVisibility(View.INVISIBLE); } if (itemBeanList.size() > pageNum * 8 + 7) { llItem8.setVisibility(View.VISIBLE); tvName8.setText(itemBeanList.get(pageNum * 8 + 7).getIconName()); ivIcon8.setBackgroundResource(itemBeanList.get(pageNum * 8 + 7).getIconID()); setIconAlpha(itemBeanList.get(pageNum * 8 + 7).isSelected(), ivIcon8); } else { llItem8.setVisibility(View.INVISIBLE); } } public void setIconAlpha(boolean isSelected, ImageView imageView) { if (isSelected) { imageView.setAlpha(1.0f); } else { imageView.setAlpha(0.4f); } } @Override public int getCount() { return pages; } @Override public Object instantiateItem(ViewGroup container, int position) { container.addView(itemViews.get(position)); return itemViews.get(position); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } }
代碼有點長,但其實不難理解。首先我們創建一個NavigationAdapter繼承于ViewPager專用的PageAdapter,并重寫其中的幾個方法。這里就要注意一個小地方了,getCount方法返回的是ViewPager的item數目,也就是頁數,而不是圖標的數目,可千萬不能搞錯了!
下面我就講解一下比較重要的方法。首先是初始化ViewPager頁面布局的方法initView:
private void initView(Listlist) { itemViews.clear(); //計算ViewPager的頁數 pages = list.size() / 9; pages = pages + 1; for (int i = 0; i < pages; i++) { pageView = View.inflate(context, R.layout.page_navigation, null); ButterKnife.bind(this, pageView); setPagerViewData(i); itemViews.add(pageView); } }
第4和第5行的代碼是根據圖標的數目來計算ViewPager的頁數,而for循環則是有多少頁我們就調用多少次View.inflate方法去創建布局。這也是ButterKnife.bind(this, pageView)要在for循環中執行的原因:我們使用的雖然都是同一個布局文件,但實際每次循環都會創建一個新的布局。
setPagerViewData方法則是給頁面中的每個item填充數據。首先判斷集合中的某一位是否有數據,有的話則設置圖片和文字,反之則隱藏item。里面的代碼基本都一個樣子,我就摘錄其中“最特別”的一段了:
if (itemBeanList.size() > pageNum * 8 + 4) { llItem5.setVisibility(View.VISIBLE); tvName5.setText(itemBeanList.get(pageNum * 8 + 4).getIconName()); ivIcon5.setBackgroundResource(itemBeanList.get(pageNum * 8 + 4).getIconID()); setIconAlpha(itemBeanList.get(pageNum * 8 + 4).isSelected(), ivIcon5); } else { llItem5.setVisibility(View.INVISIBLE); llLine2.setVisibility(View.GONE); //t圖標數不大于4個時讓第二行消失 }
如果你對于initView方法中for循環從0開始(頁碼的取值從0開始)而不是從1開始感到不解,那么看了setPagerViewData方法之后相信你就會懂了。頁碼為0時,第一頁的第一個item取到的正好是集合的0位元素;頁碼為1時,第二頁的第一個item則是集合中的第8位(1x8+1)元素,這樣,我們就可以根據具體的頁碼來將集合中的元素填充到頁面中。這里別忘了還有高度適應的問題。回頭看看我摘出來的這段代碼,當集合長度不大于4時,我們不僅要隱藏第五個item,而且要讓第二行的布局消失,這時,ViewPager的高度就只有一行了。但你肯定會問,第一頁時去掉第二行沒什么,可以到了第二頁時如果集合元素排不到第二行,第二行也會消失嗎?答案是不會的。ViewPager默認所有頁面的高度都是一致的,就算你在第二頁就去掉了第二行,它的高度也不會隨著改變。
4.4 設置導航點在MainActivity中,我們創建繪制導航點的方法。導航點是使用xml文件畫的,灰色為未選中,綠色為選中。
private Listdots = new ArrayList<>(); /** * 繪制導航點 */ private void initDot() { for (int i = 0; i < (itemBeanList.size() / 9 + 1); i++) { Log.e("Tag", itemBeanList.size() + ""); ImageView imageView = new ImageView(this); //創建一個ImageView來放置圓點,并確定ImageView的寬高 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( 20, 20); params.leftMargin = 10; params.rightMargin = 10; params.topMargin = 5; params.bottomMargin = 10; imageView.setLayoutParams(params); dots.add(imageView); //由于剛進去引導頁時出現的是第一個引導頁,故此時的導航點設置為紅色 if (i == 0) { dots.get(i).setBackgroundResource(R.drawable.dot_green); } else { dots.get(i).setBackgroundResource(R.drawable.dot_gray); } llDots.addView(imageView); } }
導航點的父布局是LinearLayout,所以我們使用LinearLayout中的LayoutParams給導航點設置上下左右的邊距。在onCreate中調用該方法就可以繪制導航點了。因為ViewPager默認顯示的是第一頁,所以我們默認第一個的導航點是選中的。
除此之外,我們還需要讓導航點起到導航的作用。這時候就可以去調用ViewPager的頁面滑動監聽了:
/** * 滑動監聽事件 */ vpNavigation.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { Log.e("Tag", "position=" + position); for (int i = 0; i < dots.size(); i++) { if (i == position) { dots.get(i).setBackgroundResource(R.drawable.dot_green); } else { dots.get(i).setBackgroundResource(R.drawable.dot_gray); } } } @Override public void onPageScrollStateChanged(int state) { } });
在選中某個頁面時就將對應的導航點改為綠色就可以了。
4.5 圖標的點擊事件頁面布局中的控件都是在適配器NavigationAdapter中初始化的,為了在MainActivity中能夠實現它們的點擊事件,我們在NavigationAdapter的構造方法中傳入一個View.onClickListener的對象作為參數,只需在MainActivity繼承View.OnClickListener接口,在創建NavigationAdapter時傳入this即可。
當然,我們還需要在NavigationAdapter中讓需要實現點擊事件的控件調用setOnClickListener方法:
private void initListener() { llItem1.setOnClickListener(onClickListener); llItem2.setOnClickListener(onClickListener); llItem3.setOnClickListener(onClickListener); llItem4.setOnClickListener(onClickListener); llItem5.setOnClickListener(onClickListener); llItem6.setOnClickListener(onClickListener); llItem7.setOnClickListener(onClickListener); llItem8.setOnClickListener(onClickListener); }
大家想一想,initListener方法應該在哪里調用呢?這里有一個坑,由于ViewPager的頁面是一頁一頁地繪制的,所以注冊控件的點擊事件也應該在for循環中,為每一頁的控件都注冊一遍:
for (int i = 0; i < pages; i++) { pageView = View.inflate(context, R.layout.page_navigation, null); ButterKnife.bind(this, pageView); setPagerViewData(i); itemViews.add(pageView); initListener(); }
好了,現在可以到MainActivity中實現監聽事件了:
@Override public void onClick(View v) { int pageNum = vpNavigation.getCurrentItem(); Log.e("Tag", "viewPager.getCurrentItem()=" + vpNavigation.getCurrentItem()); switch (v.getId()) { case R.id.ll_item1: Toast.makeText(this, itemBeanList.get(pageNum * 8 + 0).getIconName(), Toast.LENGTH_SHORT).show(); initSelected(); itemBeanList.get(pageNum * 8 + 0).setSelected(true); adapter.refresh(itemBeanList); break; case R.id.ll_item2: Toast.makeText(this, itemBeanList.get(pageNum * 8 + 1).getIconName(), Toast.LENGTH_SHORT).show(); initSelected(); itemBeanList.get(pageNum * 8 + 1).setSelected(true); adapter.refresh(itemBeanList); break; case R.id.ll_item3: Toast.makeText(this, itemBeanList.get(pageNum * 8 + 2).getIconName(), Toast.LENGTH_SHORT).show(); initSelected(); itemBeanList.get(pageNum * 8 + 2).setSelected(true); adapter.refresh(itemBeanList); break; case R.id.ll_item4: Toast.makeText(this, itemBeanList.get(pageNum * 8 + 3).getIconName(), Toast.LENGTH_SHORT).show(); initSelected(); itemBeanList.get(pageNum * 8 + 3).setSelected(true); adapter.refresh(itemBeanList); break; case R.id.ll_item5: Toast.makeText(this, itemBeanList.get(pageNum * 8 + 4).getIconName(), Toast.LENGTH_SHORT).show(); initSelected(); itemBeanList.get(pageNum * 8 + 4).setSelected(true); adapter.refresh(itemBeanList); break; case R.id.ll_item6: Toast.makeText(this, itemBeanList.get(pageNum * 8 + 5).getIconName(), Toast.LENGTH_SHORT).show(); initSelected(); itemBeanList.get(pageNum * 8 + 5).setSelected(true); adapter.refresh(itemBeanList); break; case R.id.ll_item7: Toast.makeText(this, itemBeanList.get(pageNum * 8 + 6).getIconName(), Toast.LENGTH_SHORT).show(); initSelected(); itemBeanList.get(pageNum * 8 + 6).setSelected(true); adapter.refresh(itemBeanList); break; case R.id.ll_item8: Toast.makeText(this, itemBeanList.get(pageNum * 8 + 7).getIconName(), Toast.LENGTH_SHORT).show(); initSelected(); itemBeanList.get(pageNum * 8 + 7).setSelected(true); adapter.refresh(itemBeanList); break; default: break; } } /** * 初始化圖標的選擇狀態,全部設置為未選中狀態 */ private void initSelected() { for (int i = 0; i < itemBeanList.size(); i++) { itemBeanList.get(i).setSelected(false); } }
刷新Adapter時我是重新調用了initView方法,并且傳入了修改后的數據源。下面是在Adapter中刷新的方法:
public void refresh(ListitemBeanList){ initView(itemBeanList); notifyDataSetChanged(); }
運行一下,點擊,咦,怎么沒有反應呢?PageAdapter這里有一個坑了:notifyDataSetChanged只有在ViewPager的頁數發生變化才會刷新頁面,單是某一個頁面的數據發生變化時是沒用的。但是不用著急,我們可以重寫getItemPosition方法,讓其返回POSITION_NONE,強迫viewpager重繪所有的頁面。
/** * 使Adapter能夠刷新布局 */ private int mChildCount = 0; @Override public void notifyDataSetChanged() { mChildCount = getCount(); super.notifyDataSetChanged(); } @Override public int getItemPosition(Object object) { if (mChildCount > 0) { mChildCount--; return POSITION_NONE; } return super.getItemPosition(object); }
運行一下,點擊,發現可以完美實現我們要的效果了。
4.6 雙擊取消選中實現雙擊取消選中的效果并不難,我們只需在點擊每一個圖標之前都判斷一下當前的選擇狀態,如果當前為選中的狀態,則將狀態改為未選中,反之,則改為選中。示例代碼如下:
if (itemBeanList.get(pageNum * 8 + 0).isSelected()) { initSelected(); itemBeanList.get(pageNum * 8 + 0).setSelected(false); } else { initSelected(); itemBeanList.get(pageNum * 8 + 0).setSelected(true); }
我在代碼中只設置了前面兩項,大家如果想實現所有圖標的雙擊選中事件的話就自己動一下手吧!
5、總結這個功能我前前后后花了不少時間,在參考別人代碼的基礎上總算是實現了,水平有限,如果博客中有錯漏的地方,歡迎批評指正。我的寫法還是太過簡單粗暴,不夠精巧,而且對資源的占用也比較大,如果你有更好的方法,歡迎交流學習。聽說ViewPager+GridVie也可以實現同樣的效果,接下來我再研究一下,看能不能再寫一篇博客吧!
最后是源碼下載地址:
GitHub
CSDN
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67144.html
摘要:無論是跑馬燈新聞標題還是餓了么的導航欄,它們的作用都是一樣的,那就是復用有限的屏幕空間,展示更為豐富的內容。最后附上源碼的地址參考文章之的簡單使用仿淘寶首頁的淘寶頭條垂直滾動仿餓了么首頁導航欄 在淘寶App的首頁中間位置,有一塊小小的地方在不知疲倦地循壞滾動著頭條標題(見下圖的紅框區域),這樣的設計無疑能夠在有限的手機屏幕上展示更豐富的內容。而實現這一功能需要用到的控件就是我在上一篇文...
閱讀 3073·2021-11-24 11:14
閱讀 3504·2021-11-22 15:22
閱讀 3204·2021-09-27 13:36
閱讀 715·2021-08-31 14:29
閱讀 1331·2019-08-30 15:55
閱讀 1760·2019-08-29 17:29
閱讀 1148·2019-08-29 16:24
閱讀 2409·2019-08-26 13:48