第二章:navigation項目主頁配置

  • 創建tab顯示json類

在這裏插入圖片描述

{
  "activeColor": "#ff678f",
  "inActiveColor": "#666666",
  "selectTab": 0,
  "tabs": [
    {
      "size": 24,
      "enable": true,
      "index": 0,
      "pageUrl": "main/tabs/home",
      "title": "首頁"
    },
    {
      "size": 24,
      "enable": true,
      "index": 1,
      "pageUrl": "main/tabs/sofa",
      "title": "沙發"
    },
    {
      "size": 40,
      "enable": true,
      "index": 2,
      "tintColor": "#ff678f",
      "pageUrl": "main/tabs/publish",
      "title": ""
    },
    {
      "size": 24,
      "enable": true,
      "index": 3,
      "pageUrl": "main/tabs/find",
      "title": "發現"
    },
    {
      "size": 24,
      "enable": true,
      "index": 4,
      "pageUrl": "main/tabs/my",
      "title": "我的"
    }
  ]
}
  • 自定義BottomNavigationView
package cn.example.jetpackdemo.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MenuItem;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.google.android.material.bottomnavigation.BottomNavigationItemView;
import com.google.android.material.bottomnavigation.BottomNavigationMenuView;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.bottomnavigation.LabelVisibilityMode;

import java.util.List;

import cn.example.jetpackdemo.R;
import cn.example.jetpackdemo.ui.model.BottomBar;
import cn.example.jetpackdemo.ui.model.Destination;
import cn.example.jetpackdemo.utils.AppConfig;

public class HAppBottomBar extends BottomNavigationView {
    private static int[] sIcons = new int[]{R.drawable.icon_tab_home, R.drawable.icon_tab_sofa,
            R.drawable.icon_tab_publish, R.drawable.icon_tab_find, R.drawable.icon_tab_mine};
    private BottomBar config;

    public HAppBottomBar(@NonNull Context context) {
        this(context, null);
    }

    public HAppBottomBar(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    @SuppressLint("RestrictedApi")
    public HAppBottomBar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        config = AppConfig.getBottomBarConfig();

        int[][] state = new int[2][];
        state[0] = new int[]{android.R.attr.state_selected};
        state[1] = new int[]{};
        int[] colors = new int[]{Color.parseColor(config.getActiveColor()), Color.parseColor(config.getInActiveColor())};
        ColorStateList stateList = new ColorStateList(state, colors);
        setItemTextColor(stateList);
        setItemIconTintList(stateList);
        //LABEL_VISIBILITY_LABELED:設置按鈕的文本爲一直顯示模式
        //LABEL_VISIBILITY_AUTO:當按鈕個數小於三個時一直顯示,或者當按鈕個數大於3個且小於5個時,被選中的那個按鈕文本纔會顯示
        //LABEL_VISIBILITY_SELECTED:只有被選中的那個按鈕的文本纔會顯示
        //LABEL_VISIBILITY_UNLABELED:所有的按鈕文本都不顯示
        setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED);
        List<BottomBar.TabsBean> tabs = config.getTabs();
        for (BottomBar.TabsBean tab : tabs) {
            if (!tab.isEnable()) {
                continue;
            }
            int itemId = getItemId(tab.getPageUrl());
            if (itemId < 0) {
                continue;
            }
            MenuItem menuItem = getMenu().add(0, itemId, tab.getIndex(), tab.getTitle());
            menuItem.setIcon(sIcons[tab.getIndex()]);
        }

        //此處給按鈕icon設置大小
        int index = 0;
        for (BottomBar.TabsBean tab : config.getTabs()) {
            if (!tab.isEnable()) {
                continue;
            }

            int itemId = getItemId(tab.getPageUrl());
            if (itemId < 0) {
                continue;
            }

            int iconSize = dp2Px(tab.getSize());
            BottomNavigationMenuView menuView = (BottomNavigationMenuView) getChildAt(0);
            BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(index);
            itemView.setIconSize(iconSize);
            if (TextUtils.isEmpty(tab.getTitle())) {
                int tintColor = TextUtils.isEmpty(tab.getTintColor()) ? Color.parseColor("#ff678f") : Color.parseColor(tab.getTintColor());
                itemView.setIconTintList(ColorStateList.valueOf(tintColor));
                //禁止掉點按時 上下浮動的效果
                itemView.setShifting(false);
                /**
                 * 如果想要禁止掉所有按鈕的點擊浮動效果。
                 * 那麼還需要給選中和未選中的按鈕配置一樣大小的字號。
                 *
                 *  在MainActivity佈局的AppBottomBar標籤增加如下配置,
                 *  @style/active,@style/inActive 在style.xml中
                 *  app:itemTextAppearanceActive="@style/active"
                 *  app:itemTextAppearanceInactive="@style/inActive"
                 *
                 *  <style name="active" parent="TextAppearance.AppCompat">
                 *         <item name="android:textSize">14sp</item>
                 *         <item name="android:textColor">#000000</item>
                 *     </style>
                 *
                 *     <style name="inActive" parent="TextAppearance.AppCompat">
                 *         <item name="android:textSize">14sp</item>
                 *         <item name="android:textColor">#000000</item>
                 *     </style>
                 */
            }
            index++;
        }

        //底部導航欄默認選中項
        if (config.getSelectTab() != 0) {
            BottomBar.TabsBean selectTab = config.getTabs().get(config.getSelectTab());
            if (selectTab.isEnable()) {
                int itemId = getItemId(selectTab.getPageUrl());
                //這裏需要延遲一下 再定位到默認選中的tab
                //因爲 咱們需要等待內容區域,也就NavGraphBuilder解析數據並初始化完成,
                //否則會出現 底部按鈕切換過去了,但內容區域還沒切換過去
                post(() -> setSelectedItemId(itemId));
            }
        }
    }

    private int dp2Px(int dpValue) {
        DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
        return (int) (metrics.density * dpValue + 0.5f);
    }

    private int getItemId(String pageUrl) {
        Destination destination = AppConfig.getDestConfig().get(pageUrl);
        if (destination == null) {
            return -1;
        }
        return destination.getId();
    }
}
  • 讀取json文件
package cn.example.jetpackdemo.utils;

import android.content.res.AssetManager;
import android.util.Log;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;

import cn.example.jetpackdemo.ui.model.BottomBar;
import cn.example.jetpackdemo.ui.model.Destination;
import cn.yumakeji.lib_common.global.AppGlobals;

/**
 *App的 navigation配置
 */
public class AppConfig {
    private static HashMap<String, Destination> sDestConfig;
    private static BottomBar sBottomBar;

    /**
     * 配置Fragment
     * @return
     */
    public static HashMap<String, Destination> getDestConfig() {
        if (sDestConfig == null) {
            String content = parseFile("destination.json");
            sDestConfig = JSON.parseObject(content, new TypeReference<HashMap<String, Destination>>() {
            });
        }
        return sDestConfig;
    }

    /**
     * 配置底部導航欄
     * @return
     */
    public static BottomBar getBottomBarConfig() {
        if (sBottomBar == null) {
            String content = parseFile("main_tabs_config.json");
            sBottomBar = JSON.parseObject(content, BottomBar.class);
        }
        return sBottomBar;
    }

    private static String parseFile(String fileName) {
        AssetManager assets = AppGlobals.getApplication().getAssets();
        InputStream is = null;
        BufferedReader br = null;
        StringBuilder builder = new StringBuilder();
        try {
            is = assets.open(fileName);
            br = new BufferedReader(new InputStreamReader(is));
            String line = null;
            while ((line = br.readLine()) != null) {
                builder.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (br != null) {
                    br.close();
                }
            } catch (Exception e) {

            }
        }

        return builder.toString();
    }
}
  • 創建Fragment切換幫助類NavGraphBuilder
package cn.example.jetpackdemo.utils;

import android.content.ComponentName;

import androidx.fragment.app.FragmentActivity;
import androidx.navigation.ActivityNavigator;
import androidx.navigation.NavController;
import androidx.navigation.NavGraph;
import androidx.navigation.NavGraphNavigator;
import androidx.navigation.NavigatorProvider;
import androidx.navigation.fragment.FragmentNavigator;

import java.util.HashMap;
import java.util.Iterator;

import cn.example.jetpackdemo.navigator.FixFragmentNavigator;
import cn.example.jetpackdemo.ui.model.Destination;
import cn.yumakeji.lib_common.global.AppGlobals;

public class NavGraphBuilder {
    /**
     * 內部使用的是replace替換
     *
     * @param controller
     */
    public static void build(NavController controller) {
        NavigatorProvider provider = controller.getNavigatorProvider();

        FragmentNavigator fragmentNavigator = provider.getNavigator(FragmentNavigator.class);
        ActivityNavigator activityNavigator = provider.getNavigator(ActivityNavigator.class);

        NavGraph navGraph = new NavGraph(new NavGraphNavigator(provider));

        HashMap<String, Destination> destConfig = AppConfig.getDestConfig();
        for (Destination node : destConfig.values()) {
            if (node.isIsFragment()) {
                FragmentNavigator.Destination destination = fragmentNavigator.createDestination();
                destination.setId(node.getId());
                destination.setClassName(node.getClazName());
                destination.addDeepLink(node.getPageUrl());
                navGraph.addDestination(destination);
            } else {
                ActivityNavigator.Destination destination = activityNavigator.createDestination();
                destination.setId(node.getId());
                destination.setComponentName(new ComponentName(AppGlobals.getApplication().getPackageName(), node.getClazName()));
                destination.addDeepLink(node.getPageUrl());
                navGraph.addDestination(destination);
            }
            //給APP頁面導航結果圖 設置一個默認的展示頁的id
            if (node.isAsStarter()) {
                navGraph.setStartDestination(node.getId());
            }
        }
        controller.setGraph(navGraph);
    }

    /**
     * 底部Tab切換時 使用hide()/show(),而不是replace()
     *
     * @param activity
     * @param controller
     * @param containerId
     */
    public static void build(FragmentActivity activity, NavController controller, int containerId) {
        NavigatorProvider provider = controller.getNavigatorProvider();

        //NavGraphNavigator也是頁面路由導航器的一種,只不過他比較特殊。
        //它只爲默認的展示頁提供導航服務,但真正的跳轉還是交給對應的navigator來完成的
        NavGraph navGraph = new NavGraph(new NavGraphNavigator(provider));
        //fragment的導航此處使用我們定製的FixFragmentNavigator,底部Tab切換時 使用hide()/show(),而不是replace()
        FixFragmentNavigator fragmentNavigator = new FixFragmentNavigator(activity, activity.getSupportFragmentManager(), containerId);
        provider.addNavigator(fragmentNavigator);
        ActivityNavigator activityNavigator = provider.getNavigator(ActivityNavigator.class);
        HashMap<String, Destination> destConfig = AppConfig.getDestConfig();
        Iterator<Destination> iterator = destConfig.values().iterator();
        while (iterator.hasNext()) {
            Destination node = iterator.next();
            if (node.isIsFragment()) {
                FragmentNavigator.Destination destination = fragmentNavigator.createDestination();
                destination.setId(node.getId());
                destination.setClassName(node.getClazName());
                destination.addDeepLink(node.getPageUrl());
                navGraph.addDestination(destination);
            } else {
                ActivityNavigator.Destination destination = activityNavigator.createDestination();
                destination.setId(node.getId());
                destination.setComponentName(new ComponentName(AppGlobals.getApplication().getPackageName(), node.getClazName()));
                destination.addDeepLink(node.getPageUrl());
                navGraph.addDestination(destination);
            }

            //給APP頁面導航結果圖 設置一個默認的展示頁的id
            if (node.isAsStarter()) {
                navGraph.setStartDestination(node.getId());
            }
        }

        controller.setGraph(navGraph);
    }
}
  • 定製的Fragment導航器,替換ft.replace(mContainerId, frag);爲 hide()/show()
package cn.example.jetpackdemo.navigator;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.navigation.NavDestination;
import androidx.navigation.NavOptions;
import androidx.navigation.Navigator;
import androidx.navigation.fragment.FragmentNavigator;

import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.Map;

/**
 * 定製的Fragment導航器,替換ft.replace(mContainerId, frag);爲 hide()/show()
 */
@Navigator.Name("fixfragment")
public class FixFragmentNavigator extends FragmentNavigator {
    private static final String TAG = "FixFragmentNavigator";
    private Context mContext;
    private FragmentManager mManager;
    private int mContainerId;

    public FixFragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager, int containerId) {
        super(context, manager, containerId);
        mContext = context;
        mManager = manager;
        mContainerId = containerId;
    }

    @Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        if (mManager.isStateSaved()) {
            Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
                    + " saved its state");
            return null;
        }
        String className = destination.getClassName();
        if (className.charAt(0) == '.') {
            className = mContext.getPackageName() + className;
        }
        //final Fragment frag = instantiateFragment(mContext, mManager,
        //       className, args);
        //frag.setArguments(args);
        final FragmentTransaction ft = mManager.beginTransaction();

        int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
        int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
        int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
        int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
            enterAnim = enterAnim != -1 ? enterAnim : 0;
            exitAnim = exitAnim != -1 ? exitAnim : 0;
            popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
            popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
        }

        Fragment fragment = mManager.getPrimaryNavigationFragment();
        if (fragment != null) {
            ft.hide(fragment);
        }

        Fragment frag = null;
        String tag = String.valueOf(destination.getId());

        frag = mManager.findFragmentByTag(tag);
        if (frag != null) {
            ft.show(frag);
        } else {
            frag = instantiateFragment(mContext, mManager, className, args);
            frag.setArguments(args);
            ft.add(mContainerId, frag, tag);
        }
        //ft.replace(mContainerId, frag);
        ft.setPrimaryNavigationFragment(frag);

        final @IdRes int destId = destination.getId();
        ArrayDeque<Integer> mBackStack = null;
        try {
            Field field = FragmentNavigator.class.getDeclaredField("mBackStack");
            field.setAccessible(true);
            mBackStack = (ArrayDeque<Integer>) field.get(this);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        final boolean initialNavigation = mBackStack.isEmpty();
        // TODO Build first class singleTop behavior for fragments
        final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
                && navOptions.shouldLaunchSingleTop()
                && mBackStack.peekLast() == destId;

        boolean isAdded;
        if (initialNavigation) {
            isAdded = true;
        } else if (isSingleTopReplacement) {
            // Single Top means we only want one instance on the back stack
            if (mBackStack.size() > 1) {
                // If the Fragment to be replaced is on the FragmentManager's
                // back stack, a simple replace() isn't enough so we
                // remove it from the back stack and put our replacement
                // on the back stack in its place
                mManager.popBackStack(
                        generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
                        FragmentManager.POP_BACK_STACK_INCLUSIVE);
                ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
            }
            isAdded = false;
        } else {
            ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
            isAdded = true;
        }
        if (navigatorExtras instanceof Extras) {
            Extras extras = (Extras) navigatorExtras;
            for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
                ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
            }
        }
        ft.setReorderingAllowed(true);
        ft.commit();
        // The commit succeeded, update our view of the world
        if (isAdded) {
            mBackStack.add(destId);
            return destination;
        } else {
            return null;
        }
    }

    private String generateBackStackName(int backStackindex, int destid) {
        return backStackindex + "-" + destid;
    }
}
  • xml配置
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="?attr/actionBarSize">

    <cn.example.jetpackdemo.view.HAppBottomBar
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:itemTextAppearanceActive="@style/active"
        app:itemTextAppearanceInactive="@style/inActive"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />
    <!--     如果想要禁止掉所有按鈕的點擊浮動效果。

          @style/active,@style/inActive 在style.xml中
          app:itemTextAppearanceActive="@style/active"
          app:itemTextAppearanceInactive="@style/inActive"
     -->
    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
  • 調用

public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {

    private NavController navController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        BottomNavigationView navView = findViewById(R.id.nav_view);

        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
//        AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
//                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
//                .build();
//        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
//        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
//        NavigationUI.setupWithNavController(navView, navController);

        /**
         * 使用replace方式
         */
        /*
             navController = Navigation.findNavController(this, R.id.nav_host_fragment);
             NavigationUI.setupWithNavController(navView, navController);
             NavGraphBuilder.build(navController);
             navView.setOnNavigationItemSelectedListener(this);
         */

        /**
         * 使用hide()/show(),而不是replace()
         *
         */
        Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
        navController = NavHostFragment.findNavController(fragment);
        NavGraphBuilder.build(this, navController, fragment.getId());
        navView.setOnNavigationItemSelectedListener(this);
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();

    }
	
	/**
     * 底部tab綁定fragment
     * @param item
     * @return
     */
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
   	    HashMap<String, Destination> destConfig = AppConfig.getDestConfig();
        Iterator<Map.Entry<String, Destination>> iterator = destConfig.entrySet().iterator();
        //遍歷 target destination 是否需要登錄攔截
        while (iterator.hasNext()) {
            Map.Entry<String, Destination> entry = iterator.next();
            Destination value = entry.getValue();
            if (value != null && value.isNeedLogin() && value.getId() == item.getItemId()) {
                Toast.makeText(AppGlobals.getApplication(), "當前頁面需要登錄", Toast.LENGTH_LONG).show();
                return false;
            }
        }
        navController.navigate(item.getItemId());
        return !TextUtils.isEmpty(item.getTitle());
    }
}
  • Fragment配置

@FragmentDestination(pageUrl = "main/tabs/home", asStarter = true)
public class HomeFragment extends Fragment {}

@FragmentDestination(pageUrl = "main/tabs/my", needLogin = true,asStarter = false)
public class MyFragment extends Fragment {}

@ActivityDestination(pageUrl = "main/tabs/publish", asStarter = false)
public class PublishActivity extends AppCompatActivity {}

  • 獲取全局上下文幫助類
package cn.yumakeji.lib_common.global;

import android.app.Application;

import java.lang.reflect.InvocationTargetException;

/**
 * 這種方式獲取全局的Application 是一種拓展思路。
 * <p>
 * 對於組件化項目,不可能把項目實際的Application下沉到Base,而且各個module也不需要知道Application真實名字
 * <p>
 * 這種一次反射就能獲取全局Application對象的方式相比於在Application#OnCreate保存一份的方式顯示更加通用了
 */
public class AppGlobals {
    private static Application sApplication;

    public static Application getApplication() {
        if (sApplication == null) {
            try {
                sApplication = (Application) Class.forName("android.app.ActivityThread")
                        .getMethod("currentApplication")
                        .invoke(null, (Object[]) null);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return sApplication;
    }
}
  • gradle配置

//***************************基礎******************************
//兼容包
api 'androidx.appcompat:appcompat:1.1.0'
//material組件
api 'com.google.android.material:material:1.1.0'
//約束佈局
api 'androidx.constraintlayout:constraintlayout:1.1.3'
//navigation導航
api 'androidx.navigation:navigation-fragment:2.2.2'
api 'androidx.navigation:navigation-ui:2.2.2'
//包含了 viewmodel 和 livedata
api 'androidx.lifecycle:lifecycle-extensions:2.2.0'
api 'androidx.legacy:legacy-support-v4:1.0.0'
//***************************基礎結束******************************

api project(path: ':lib_navannotation')
annotationProcessor project(path: ':lib_navcompiler')
implementation 'com.alibaba:fastjson:1.2.68'
api project(path: ':lib_common')
  • 效果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章