Jetpack 實戰:神奇寶貝

2021-02-26 ByteCode
前言

在之前的文章中提到了很多次 Jetpack 實戰項目 PokemonGo(神奇寶貝),PokemonGo 基於 MVVM 架構和 Repository 設計模式開發的一個小型 App,包含了最新的架構和最新的技術,是一個非常好的學習項目,已經上傳到了 GitHub 歡迎前去查看
https://github.com/hi-dhl/PokemonGo

先來介紹一下 Jetpack 實戰項目 PokemonGo, 包含了以下功能:

使用 Data Mapper 分離數據源 和 UIKotlin Flow 結合  Retrofit2 + RoomActivity 、Fragment、ViewModel 結合 Flow 三種使用方式使用 ConflatedBroadcastChannel 實現 DB 搜索使用 StateFlow 實現 NetWork 搜索使用 ViewModel、LiveData、DataBinding 協同工作增加了使用 sealed 在 Flow 基礎上封裝成功或者失敗處理增加了 Kotlin 常用操作符 debounce 、filter 、flatMapLatest 、 distinctUntilChanged 的使用增加 Fragment 1.2.0 上重要的更新:通過 Fragment 的構造函數傳遞參數,以及 FragmentFactory 和 FragmentContainerView 的使用

PokemonGo 包含的技術:

Gradle Versions Plugin :檢查依賴庫是否存在最新版本Kotlin + Coroutines + Flow :flow 是對 Kotlin 協程的擴展,讓我們可以像運行同步代碼一樣運行異步代碼DataBinding :以聲明方式將可觀察數據綁定到界面上Room :在 SQLite 上提供了一個抽象層,流暢地訪問 SQLite 資料庫ViewModel :以注重生命周期的方式管理界面相關的數據Andriod KTX :編寫更簡潔、慣用的 Kotlin 代碼Retrofit2 & OkHttp3 :用於請求網路數據Coil :基於 Kotlin 開發的首個圖片加載庫material-components-android :模塊化和可定製的材料設計 UI 組件Motionlayout :MotionLayout 是一種布局類型,可幫助您管理應用中的動畫JProgressView :一個小巧靈活可定製的進度條,支持圖形:圓形、圓角矩形、矩形等等

如果之前對這些技術沒有接觸過,或者只是聽說,對閱讀本文沒有什麼影響,本文會對這些技術結合著項目 PokemonGo 來分析,為了文章的簡潔性,本文不會細究技術細節,因為每個技術都需要花好幾篇文章才能分析清楚,我會在後續的文章去詳細分析。

如何檢查依賴庫最新版本

在之前的文章 再見吧 buildSrc, 擁抱 Composing builds 提升 Android 編譯速度 分析過,到目前為止大概管理 Gradle 依賴提供了 4 種不同方法:

手動管理 :在每個 module 中定義插件依賴庫,每次升級依賴庫時都需要手動更改(不建議使用)使用 ext 的方式管理插件依賴庫 :這是 Google 推薦管理依賴的方法Kotlin + buildSrc:支持自動補全和單擊跳轉,依賴更新時 將重新 構建整個項目Composing builds:支持自動補全和單擊跳轉,依賴更新時 不會重新 構建整個項目

新版的 AndroidStudio 只支持 ext 的方式手動方式管理  檢查依賴庫是否存在最新版本,不支持 buildSrc、gradle-wrapper 版本的檢查。

滿足不了 PokemonGo 項目的需求,在 PokemonGo 項目中採用 buildSrc 方式去管理所有依賴庫,因為 PokemonGo 項目採用單模塊結構,而且支持 自動補全單擊跳轉 很方便,所這裡用到了 Gradle Versions Plugin 插件去檢查依賴庫的最新版本,檢查結果如下所示:

The following dependencies have later release versions:
 - androidx.swiperefreshlayout:swiperefreshlayout [1.0.0 -> 1.1.0]
     https://developer.android.com/jetpack/androidx
 - com.squareup.okhttp3:logging-interceptor [3.9.0 -> 4.7.2]
     https://square.github.io/okhttp/
 - junit:junit [4.12 -> 4.13]
     http://junit.org
 - org.koin:koin-android [2.1.5 -> 2.1.6]
 - org.koin:koin-androidx-viewmodel [2.1.5 -> 2.1.6]
 - org.koin:koin-core [2.1.5 -> 2.1.6]

Gradle release-candidate updates:
 - Gradle: [6.1.1 -> 6.5.1]

會列出所有需要更新的依賴庫的最新版本,並且 Gradle Versions Plugin 比 AndroidStudio 所支持的更加全面:

支持 buildSrc 方式管理依賴庫最新版本檢查

那麼如何使用呢?只需要三步

1.將 PokemonGo 項目根目錄 checkVersions.gradle 文件拷貝到你的項目根目錄下面2.在項目的根目錄 build.gradle 文件夾內添加以下代碼
apply from: './checkVersions.gradle'
buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.github.ben-manes:gradle-versions-plugin:0.28.0"
    }
}

./gradlew dependencyUpdates

會在當前目錄下生成 build/dependencyUpdates/report.txt 文件。

MVVM 架構

Jetpack 實戰項目 PokemonGo 基於  MVVM 架構和 Repository 設計模式,如今幾乎所有的 Android 開發者至少都聽過 MVVM 架構,在谷歌 Android 團隊宣布了 Jetpack 的視圖模型之後,它已經成為了現代 Android 開發模式最流行的架構之一,如下圖所示:

MVVM 有助於將應用程式的業務邏輯與 UI 完全分開。如果業務邏輯與 UI 邏輯之間的聯繫非常緊密,那麼維護將很困難,由於很難重用業務邏輯,因此編寫單元測試代碼非常困難,一堆重複的代碼和複雜的邏輯。

Jetpack 的視圖模型的 MVVM 架構由 View + DataBinding + ViewModel + Model 組成。

DataBinding

DataBinding(數據綁定)實際上是 XML 布局中的另一個視圖結構層次,視圖 (XML) 通過數據綁定層不斷地與 ViewModel 交互。

我們來看一個例子,首頁上有個 RecyclerView 用來展示神奇寶貝數據(名字、圖片、點擊事件等等),每一個 item 對應一個 ViewHolder,來看一下 ViewHolder 的實現。

class PokemonViewModel(view: View) : DataBindingViewHolder<PokemonListModel>(view) {
    private val mBinding: RecycleItemPokemonBinding by viewHolderBinding(view)

    override fun bindData(data: PokemonListModel, position: Int) {
        mBinding.apply {
            pokemon = data
            executePendingBindings()
        }
    }
}

正如你所看到的,由於使用了數據綁定,ViewHolder 裡面的代碼變的非常簡單,可能這個例子不夠明顯,我們來看一個勁爆的,點擊首頁每一個 item 會跳轉到詳情頁面,詳情頁面如下圖所示:

詳情頁面(DetailActivity)展示了神奇寶貝的詳細數據,先查詢資料庫,如果沒有找到,讀取網路數據然後保存到資料庫,由於使用了數據綁定,代碼變得非常簡單,如下所示:

class DetailActivity : DataBindingAppCompatActivity() {

    private val mBindingActivity: ActivityDetailsBinding by binding(R.layout.activity_details)
    private val mViewModel: DetailViewModel by viewModels()
    lateinit var mPokemonModel: PokemonListModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBindingActivity.apply {
            mPokemonModel = requireNotNull(intent.getParcelableExtra(KEY_LIST_MODEL))
            pokemonListModel = mPokemonModel
            lifecycleOwner = this@DetailActivity
            viewModel = mViewModel.apply {
                fectchPokemonInfo(mPokemonModel.name)
                    .observe(this@DetailActivity, Observer {})
            }
        }
    }
}

正如你所見 DetailActivity 代碼變得非常簡單,如果以後我們想要改變網絡的 URL、Model、獲取或保存數據的方式等等,我們不需要改變 DetailActivity 中的任何代碼。

ViewModel

ViewModel 是 MVVM 架構中非常重要的設計,它在 activities 或 fragments 和業務邏輯中起到了非常重要的作用,它不依賴於 UI 組件,使得單元測試更加容易,ViewModel 以生命周期的方式管理界面相關的數據,直到 Activity 被銷毀。

LiveData 與 ViewModel 具有很好的協同作用,LiveData 持有從數據源獲取到的數據,並且它可以被 DataBinding 組件觀察,當 Activity 被銷毀時,它將被取消訂閱。

而詳情頁面(DetailActivity) 代碼之所以能這麼簡單得益於 ViewModel、LiveData、DataBinding 協同工作, 我們來看一下 ViewModel 代碼。

class DetailViewModel @ViewModelInject constructor(
    val polemonRepository: Repository
) : ViewModel() {
    private val _pokemon = MutableLiveData<PokemonInfoModel>()
    val pokemon: LiveData<PokemonInfoModel> = _pokemon

    @OptIn(ExperimentalCoroutinesApi::class)
    fun fectchPokemonInfo(name: String) = liveData<PokemonInfoModel> {
        polemonRepository.featchPokemonInfo(name)
        .collectLatest {
                _pokemon.postValue(it)
                emit(it)
            }
        ..
        // 省略部分代碼,
    }
}

activity_details.xml 代碼

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="viewModel"
            type="com.hi.dhl.pokemon.ui.detail.DetailViewModel" />

    </data>
    
    .
    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/weight"
        android:text="@{viewModel.pokemon.getWeightString}"/>
    .
    
</layout>

這是獲取神奇寶貝的詳細信息,通過 DataBinding 以聲明方式將數據(神奇寶貝的體重)綁定到界面上,更多使用參考項目中的代碼。

Repository

Repository 設計模式是最流行、應用最廣泛的設計模式之一,在 Repository 層獲取網絡數據,並將數據存儲到資料庫中,在這一層中有兩個非常重要的成員 Paging3 庫中的 RemoteMediator 和 Data Mappers。

RemoteMediator

RemoteMediator 是 Paging3 當中一個非常重要的成員,用於實現 資料庫網絡 訪問,所以這裡是對之前的文章一個補充。

RemoteMediator 很重要,需要單獨花一篇文章去分析,為了節省篇幅,在這裡不會詳細的去分析它,如果對 RemoteMediator 不太理解沒有關係,我會在後續的文章裡面詳細的分析它。

項目中網絡訪問用的是 Retrofit2 & OkHttp3 用來請求網絡數據,使用 Room 作為資料庫存儲,將獲得的數據保存到資料庫中,Room 在 SQLite 上提供了一個抽象層,流暢地訪問 SQLite 資料庫,同時擁有了 SQLite 全部功能,在編譯的時候進行錯誤檢查。

@OptIn(ExperimentalPagingApi::class)
class PokemonRemoteMediator(
    val api: PokemonService,
    val db: AppDataBase
) : RemoteMediator<Int, PokemonEntity>() {
    val mPageKey = 0
    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, PokemonEntity>
    ): MediatorResult {
        try {

            .
            val pageKey = when (loadType) {
                // 首次訪問 或者調用 PagingDataAdapter.refresh()
                LoadType.REFRESH -> null
                // 在當前加載的數據集的開頭加載數據時
                LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
                // 在當前數據集末尾添加數據
                LoadType.APPEND -> {
                    .
                    if (remoteKey == null || remoteKey.nextKey == null) {
                        return MediatorResult.Success(endOfPaginationReached = true)
                    }
                    remoteKey.nextKey
                }
            }

            .
            // 使用 Retrofit2 獲取網絡數據
            val page = pageKey ?: 0
            val result = api.fetchPokemonList(
                state.config.pageSize,
                page * state.config.pageSize
            ).results
            

            ..

            db.withTransaction {
                if (loadType == LoadType.REFRESH) { // 當首次加載,或者下拉刷新的時候,清空當前數據 }
                .
                // 存儲獲取到的數據
                remoteKeysDao.insertAll(entity)
                pokemonDao.insertPokemon(item)
            }

            return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
        } catch (e: IOException) {
            return MediatorResult.Error(e)
        } catch (e: HttpException) {
            return MediatorResult.Error(e)
        }
    }
}

注意:使用了 @OptIn(ExperimentalPagingApi::class) 需要在 App 模塊 build.gradle 文件內添加以下代碼。

android {
    kotlinOptions {
        freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
    }
}

在 RemoteMediator 的實現類 PokemonRemoteMediator 中的核心部分是關於參數 LoadType 的判斷。

LoadType.REFRESH:首次訪問 或者調用 PagingDataAdapter.refresh() 觸發,這裡不需要做任何操作,返回 null 就可以LoadType.PREPEND:在當前列表頭部添加數據的時候時觸發,實際在項目中基本很少會用到直接返回  MediatorResult.Success(endOfPaginationReached = true) ,參數 endOfPaginationReached 表示沒有數據了不在加載LoadType.APPEND:下拉加載更多時觸發,這裡獲取下一頁的 key, 如果 key 不存在,表示已經沒有更多數據,直接返回 MediatorResult.Success(endOfPaginationReached = true) 不會在進行網絡和資料庫的訪問

接下來的邏輯和之前請求網絡數據的邏輯沒有什麼區別了,使用 Retrofit2 獲取網絡數據,然後使用 Room 將數據保存到資料庫中。

接下來聊一下 Repository 中另外一個重要的成員 Data Mapper,在項目中起到了非常的重要,在一個快速開發的項目中,為了越快完成第一個版本交付,下意識的將數據源和 UI 綁定到一起,當業務逐漸增多,數據源變化了,上層也要一起變化,導致後期的重構工作量很大,核心的原因耦合性太強了。

Data Mapper(個人建議)

Data Mapper 的意識非常重要,在項目中起到了非常的重要,使用 Data Mapper 分離數據源的 Model 和 頁面顯示的 Model,不要因為數據源的增加、修改或者刪除,導致上層頁面也要跟著一起修改,換句話說使用 Data Mapper 做一個中間轉換,如下圖所示,來源於網絡:

使用 Data Mapper(數據映射)優點如下:

糟糕的後端實現不會影響上層的業務 ( 想像一下,如果你被迫執行2個網絡請求,因為後端不能在一個請求中提供你需要的所有信息,你會讓這個問題影響你的整個代碼嗎? )Data Mapper 便於做單元測試,確保不會因為數據源的變化,而影響上層的業務

如果在一個大型項目中直接使用 Data Mapper 會有適得其反的效果,所以需要結合設計模式來完善,這不在本文討論範圍之內,其實在這裡我想表達是,不要因為快速實現某個功能,下意識的將數據源的 model 和 UI 綁定在一起。

Data Mappe 實現方式有很多種,可以手動實現,也可以通過引入第三方框架,其中有名框架  modelmapper ,在 PokemonGo 項目中是手動實現的。

Kotlin Flow

停止使用 RxJava,嘗試一下 Flow,不僅簡單而且功能很強大,Retrofit2 和 Room 也都提供了對應的支持。

Flow 庫是在 Kotlin Coroutines 1.3.2 發布之後新增的庫,也叫做異步流,類似 RxJava 的 Observable,在 PokemonGo 項目中也用到了 Flow。

override suspend fun featchPokemonInfo(name: String): Flow<PokemonInfoModel> {
    return flow {
        val pokemonDao = db.pokemonInfoDao()
        var infoModel = pokemonDao.getPokemon(name)
        // 查詢資料庫是否存在,如果不存在請求網絡
        if (infoModel == null) {
            // 網絡請求
            val netWorkPokemonInfo = api.fetchPokemonInfo(name)
            .
            pokemonDao.insertPokemon(infoModel) // 插入更新資料庫
        }

        val model = mapper2InfoModel.map(infoModel) // 數據轉換
        emit(model)
    }.flowOn(Dispatchers.IO)
}

在這裡做了三件事:

將數據源的 Model 轉換為頁面顯示的 Model依賴注入

Hilt、Dagger、Koin 等等都是依賴注入庫,使用依賴注入庫有以下優點:

依賴注入庫會自動釋放不再使用的對象,減少資源的過度使用。在配置 scopes 範圍內,可重用依賴項和創建的實例,提高代碼的可重用性,減少了很多模板代碼。

在 PokemonGo 項目中使用的是 Hilt,Hilt 是在 Dagger 基礎上進行開發的,減少了在項目中進行手動依賴,Hilt 集成了 Jetpack 庫和 Android 框架類,並刪除了大部分模板代碼,讓開發者只需要關注如何進行綁定,同時 Hilt 也繼承了 Dagger 優點,編譯時正確性、運行時性能、並且得到了 Android Studio 的支持,來看一下 Hilt 與 Room 在一起使用的例子。

@Module
@InstallIn(ApplicationComponent::class)
object RoomModule {

    /**
     * @Provides 常用於被 @Module 註解標記類的內部的方法,並提供依賴項對象。
     * @Singleton 提供單例
     */
    @Provides
    @Singleton
    fun provideAppDataBase(application: Application): AppDataBase {
        return Room
            .databaseBuilder(application, AppDataBase::class.java, "dhl.db")
            .fallbackToDestructiveMigration()
            .allowMainThreadQueries()
            .build()
    }
    
    @Singleton
    @Provides
    fun provideTasksRepository(
        db: AppDataBase
    ): Repository {
        return PokemonFactory.makePokemonRepository(db)
    }
}

這裡需要用到 @Module 註解,使用 @Module 註解的普通類,在其內部提供 Room 的實例,更多使用可以查看 PokemonGo 項目。

小巧靈活的進度條

神奇寶貝詳情頁的進度條使用的是 JProgressView :一個小巧靈活可定製的進度條,支持圖形:圓形、圓角矩形、矩形等等,效果如下圖所示:
https://github.com/hi-dhl/JProgressView

起源於當時想用一個現成的庫,但是在網上找了很多,沒有一個合適自己的,要不大而全,要不作者好久沒更新了,要不不兼容 DataBinding,於是乎就自己封裝了一個小巧靈活的進度條,項目長期維護並持續更新,如果有更好的建議歡迎告知我,JProgressView 使用非常的簡單,根據自己的需求去配置即可。

<com.hi.dhl.jprogressview.JProgressView
    android:layout_width="match_parent"
    android:layout_height="18dp"
    android:layout_below="@+id/exp"
    android:translationZ="100dp"
    app:maxProgressValue="@{viewModel.pokemon.maxExp}"
    app:progressValue="@{viewModel.pokemon.exp}"
    app:progress_animate_duration="@integer/progress_animate_duration"
    app:progress_color="@color/color_progress_4"
    app:progress_color_background="@color/color_progress_bg"
    app:progress_paint_bg_width="@dimen/circle_stroke_width"
    app:progress_paint_value_width="@dimen/circle_stroke_width"
    app:progress_text_color="@android:color/black"
    app:progress_text_size="@dimen/text_size_12sp"
    app:progress_type="@integer/porgress_tpye_round_rect" />

名稱值類型默認值備註progress_typeinteger圓形:1矩形:0;矩形:0;矩形:0progress_animate_durationinteger2000動畫運行時間progress_colorcolorColor.GRAY當前進度顏色progress_color_backgroundcolorColor.GRAY進度條背景顏色progress_paint_bg_widthdimen10進度條背景畫筆的寬度progress_paint_value_widthdimen10當前進度畫筆的寬度progress_text_colorcolorColor.BLUE進度條上的文字的顏色progress_text_sizedimensp2Px(20f)進度條上的文字的大小progress_text_visibleboolean默認不顯示:false是否顯示文字progress_valueinteger0當前進度progress_value_maxinteger100當前進度條的最大值

進度條 JProgressView 已經上傳到倉庫,歡迎前去查看
https://github.com/hi-dhl/JProgressView

全文到這裡就結束了,為了節省篇幅,更多技術細節會在後續的系列文章中分析。

PokemonGo 倉庫地址:
https://github.com/hi-dhl/PokemonGo


推薦閱讀

最後推薦我一直在更新維護的項目和網站:

最新的 AndroidX Jetpack 相關組件的實戰項目 以及 原理分析的文章
https://github.com/hi-dhl/AndroidX-Jetpack-Practice

LeetCode / 劍指 offer / 國內外大廠面試題 / 多線程 題解,語言 Java 和 kotlin,包含多種解法、解題思路、時間複雜度、空間複雜度分析

劍指 offer:https://offer.hi-dhl.com
LeetCode:https://leetcode.hi-dhl.com

最新 Android 10 源碼分析系列文章
https://github.com/hi-dhl/Android10-Source-Analysis

一系列國外的技術文章,每篇文章都會有譯者思考部分,對原文的更加深入的分析
https://github.com/hi-dhl/Technical-Article-Translation

「為網際網路人而設計,國內國外名站導航」涵括新聞、體育、生活、娛樂、設計、產品、運營、前端開發、Android 開發等等網址
https://site.51git.cn

相關焦點

  • 二次元入侵:東京神奇寶貝餐廳
    我們聽說東京市中心有一家神奇寶貝餐廳,好奇的想知道神奇寶貝嘗起來到底有沒有它看起來那麼好,我們打算拜訪一下這家名為「皮卡丘咖啡」的神奇寶貝餐廳。  這家神奇寶貝餐廳坐落在時尚休閒的六本木地區。你會發現這家餐廳在毛利塔的52層。樓層這麼高的缺點就是,顧客還要額外的交觀光費才能享用到「神奇寶貝」這頓大餐。
  • 神奇寶貝:煤炭龜
    也同在相對應的動畫《神奇寶貝超世代》當中登場。設計理念來自烏龜,會在身體裡燃燒石炭產生能量,遇到危險的時候鼻孔與背部會噴出煙霧,令對手不知所措。煤炭龜可以從鼻孔與龜殼頂端噴出煙霧,並發出火車鳴笛般的聲音。與呆火駝、噴火駝不同,煤炭龜體內是燃燒煤炭而不是巖漿,但是因為身體構造類似,因此可以透過遺傳的方式來學會噴火。
  • 神奇寶貝:進化前後差距最大的五隻精靈
    在神奇寶貝中,進化是變強的一個主要手段,尤其是一些精靈如果不進化,就根本派不上用場,可如果進化了,一下子就能成為最頂尖的神奇寶貝,那麼在神奇寶貝中
  • 東京神奇寶貝中心熄燈,加油皮卡丘
  • 神奇寶貝:這5隻神奇寶貝胃口太大,最後一個永遠吃不飽
    神奇寶貝中有非常多可愛又迷人的寶可夢,他們不僅在關鍵時刻能夠出場戰鬥,平日裡也是非常有趣,討人喜愛,而動畫中有幾隻出了名的吃貨,他們簡直就像我們現實生活中的大胃王一樣能吃
  • 狩獵神奇寶貝手機遊戲 印度人也為之癲狂
    他告訴《印度時報》(The Times of India),他有時會在地鐵站周圍狩獵神奇寶貝,有時在列車內狩獵。    約希說:「人們知道我在幹嘛,因此對我點點頭,所以我不是唯一瘋狂的人。」    不過,玩這款遊戲確實要注意安全。約希表示,他有一次玩這款遊戲沒注意,太接近地鐵月臺的邊緣,當警衛吹哨警告時他才警覺。
  • 【腦洞】歷代失戀過的神奇寶貝,你知道多少?
    兩天剛過完了光棍節,趁著這餘熱還沒有散去,我們來聊一聊神奇寶貝中有哪些傢伙失戀了。1、喵喵後來蓮帽小童藉助水之石的力量進化為樂天河童時,性格變得開朗起來,也喜歡上大嘴娃了,可是大嘴娃卻不喜歡活潑的樂天河童了,反而又喜歡上一隻路過的傻傻可達鴨了,看來這隻大嘴娃對於憨厚型的神奇寶貝情有獨鍾啊。5、森林蜥蜴
  • 【新遊評測】《恐龍神奇寶貝》試玩評測:Q版馴龍高手養成記
    2015手遊版另類侏羅紀莊園——小奧遊戲《恐龍神奇寶貝》進入24小時全渠道首發倒計時!作為史上最新最萌的「Q版馴龍高手養成記」代表,今日獨家遊戲試玩評測新鮮出爐,搶先了解養恐龍建莊園基本規則,確保明日首發妥妥一鳴驚人,趕快來看看!打開《恐龍神奇寶貝》,映入眼帘的一派碧湖綠草藍天讓人格外舒爽。
  • 穿上連帽衫的超激萌神奇寶貝!
    神奇寶貝配上連帽衫時會是什麼樣的逗趣形象呢?
  • 神奇寶貝:麒麟奇
    麒麟奇是日本任天堂公司開發的一款掌機遊戲《口袋妖怪》系列和根據它改編的一部動畫《神奇寶貝》中登場的角色中的怪獸之一
  • 神獸級別神奇寶貝:鳳王
    鳳王是小編最喜歡的鳥類神奇寶貝之一了,那姿態那叫一個帥啊。
  • 盤點小智收服的七隻非常稀有的神奇寶貝,最後一隻你猜到了!
    準神獸黏美露龍小智收服的第二隻準神獸黏美露龍,它有兩條有力的觸角和巨大的尾巴,個性十分溫順,多棲息在溼潤的沼澤地帶,是擁有準傳說類龍系屬性的神奇寶貝,小智的黏美露龍在旅途戰鬥中掌握了很多龍系的強力絕招異色貓頭夜鷹異色的神奇寶貝出現概率是非常低的,而小智的這隻異色貓頭夜鷹,在印象中也是十分聰明少見的神奇寶貝。
  • XR情報局:夏天過去了,你去《神奇寶貝樂園》玩過嗎?
    如果想,就跟我一起去這個神奇寶貝虛擬樂園看看吧!據悉,日本開發者將每年都會舉辦的「夏日祭」慶典搬到了線上,截止本月底,任何人在這個《神奇寶貝》主題的虛擬樂園裡,都可以闖關交友,體驗各種遊樂園項目!媽媽我又可以了!這不就是一款遊戲嗎,跟遊樂園有什麼關係?
  • 外國網友因太愛POKEMON,竟然做出現實版神奇寶貝,真是妙出境界…
    去年Pokemon火到一塌糊塗的地步,足以證明神奇寶貝這個動漫形象在年輕人心中到底地位有多高。
  • 神奇寶貝:小次郎放生的5隻寶可夢,1隻忽然進化,2隻忍痛離別
    看過《神奇寶貝》的小夥伴們想必都聽說過小智的外號吧——放生大師,可能由於小智是男主角吧,所以要為他營造一種完美的人設吧,小智總是會因為各種原因放生本屬於他的神奇寶貝
  • 日本4大IP:海賊王是新霸主,火影完結依然火,神奇寶貝荼毒深,龍珠地位很尷尬
    藍色:海賊王紅色:火影黃色:龍珠綠色:神奇寶貝好了,可以看出在被搜集的國家中,在東亞海賊王擁有很大的優勢,尤其是在日本,幾乎是壓倒性的優勢,5個最喜歡的海賊王的地區是,日本,韓國,泰國,香港,和阿拉伯聯合大公國。雖然沒有中國大陸的數據
  • 一場走心的直播:小黃人VS神奇寶貝,來一波回憶殺!
    我們的童年沒有如今這麼多製作精良的3D動畫,但是諸如中華小當家、神奇寶貝和數碼寶貝。每天中午最期待的事情就是一邊扒飯一邊眼巴巴的守著電視(星空衛視,你們懂得)嘛,估計這也是為啥千尋醬小學就戴上眼睛的原因吧...最會賣萌的皮卡丘,一代萌神,皮卡皮,皮卡丘~~~~~~~已經成為新一代鬼畜素材。
  • 神奇寶貝:還記得這些稀有精靈嗎?三個靠膚色變化,他堪比人類
    《口袋妖怪》又稱《神奇寶貝》或者《寵物小精靈》,相信90後00後的朋友們都是相當的熟悉了。
  • 神奇寶貝:那些被惡搞的進化梗,你當年有沒有信以為真?
    在神奇寶貝中,大部分寶可夢都能進化,進化後也會保留著之前的一些特徵。
  • 神奇寶貝:假如火箭隊的武藏小次郎結了婚,天天秀恩愛甜死人
    小時候看《神奇寶貝》(寵物小精靈、精靈寶可夢)的時候,一直覺得火箭隊的武藏和小次郎好像一對。