<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【從零開始擼一個App】RecyclerView的使用

      目標

      前段時間打造了一款簡單易用功能全面的圖片上傳組件,現在就來將上傳的圖片以圖片集的形式展現到App上。出于用戶體驗考慮,加載新圖片采用[無限]滾動模式,Android平臺上我們優選RecyclerView組件。

      顯示圖片,用的自然是ImageView,然而它并不支持直接加載網絡圖片,需要先通過其它網絡組件(如HttpURLConnectionokhttp3等)將圖片獲取到本地,得到BitMap數據,然后通過setImageBitmap()加載。
      ImageView也有setImageURI(Uri uri)方法,這里uri的命名容易讓人產生錯覺,其實只能是本地文件路徑。

      所幸,一些開源組件封裝了繁瑣的網絡操作和緩存策略,提供了易用的API。這里我選擇了Glide

      實現

      加載更多

      項布局

      有兩個,一個用于列表中各個圖片顯示,一個顯示加載更多/已全部加載放置在列表最末提示用戶。

      <!--圖片-->
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical">
      
          <ImageView
              android:id="@+id/thumbnail_view"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:scaleType="centerCrop"/>
      </LinearLayout>
      
      <!--loadmore-->
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:gravity="center">
      
          <TextView
              android:id="@+id/tv_load_more"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="正在加載更多" />
      </LinearLayout>
      

      RecyclerView.Adapter

      RecyclerView的設計模式網上資料很多,此處不再贅述。先實現RecyclerView.Adapter

      class ThumbnailListAdapter(
          private val thumbnails: List<Thumbnail>,
          private val totalCount: Long,
          private val context: Context
      ) :
          RecyclerView.Adapter<ThumbnailListAdapter.ThumbnailViewHolder>() {
      
          // 調用若干次
          override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThumbnailViewHolder {
              // viewType就是通過getItemViewType得到的
              val itemView = LayoutInflater.from(context).inflate(viewType, parent, false)
              return ThumbnailViewHolder(itemView)
          }
      
          // 搞分頁/瀑布加載的同學不要把這個和數據庫的總數量搞混,這里的itemCount表示現在內存中數據量
          // 我們可以[從后端]獲取新數據添加到數據集,以實現loadmore功能
          override fun getItemCount(): Int {
              return if (thumbnails.isNotEmpty())
                  thumbnails.size + 1 // +1 是因為除了thumbnails數據集之外,還有個寫死的loadmore項
              else
                  0
          }
      
          // R.layout.xxx 是Int類型,可以直接返回
          override fun getItemViewType(position: Int): Int {
              return if (position < thumbnails.size)
                  R.layout.list_thumbnail_image // 正常圖片顯示
              else
                  R.layout.list_loadmore_footer // 末尾loadmore
          }
      
          // 有屏幕外item進入屏幕時就會調用
          override fun onBindViewHolder(holder: ThumbnailViewHolder, position: Int) {
              if (position < thumbnails.size) {
                  Glide.with(context)
                      .load(thumbnails[position].uri)
                      .into(holder.itemView.thumbnail_view)
              } else {
                  if (thumbnails.size >= totalCount)
                      holder.itemView.tv_load_more.text = "全部加載完畢"
              }
          }
          
          // 必須這么繼承一下
          class ThumbnailViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
      }
      

      滾動監聽

      為RecyclerView添加滾動監聽,在合適的時候加載新數據到數據集中。

      recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() {
          override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
              super.onScrollStateChanged(recyclerView, newState)
              // 已經在加載則跳過
              if (!_thumbnailsLoading) {
                  // 找到最后可見項的索引
                  val lastPos = layoutManager.findLastVisibleItemPosition()
                  val sum = adapter.itemCount
                  // 當快接近末尾項時(這里差額10,表示再顯示10個item就沒數據了)獲取新數據
                  if (newState == RecyclerView.SCROLL_STATE_IDLE && sum - lastPos <= 10) {
                      vm.thumbnails.addAll(vm.getMoreAlbumCovers()) // 加載新數據到數據集中
                      _thumbnailsLoading = true
                  }
              }
          }
      })
      

      不要將上面預加載數據和Glide的預加載圖片混淆起來,拿到數據,和通過數據中的uri獲取圖片并下載,這是兩個步驟。Glide專門針對RecyclerView提供了預加載方案,是為了減少滑動時圖片還未從網絡請求導致的等待加載情況,目前只支持LinearLayoutManager或其子類布局

      布局

      StaggeredGridLayoutManager

      按列瀑布流顯示圖片。簡單地將RecyclerView的layoutManager設為StaggeredGridLayoutManager實例即可,注意StaggeredGridLayoutManager目前還是beta版。

      val sgLayoutManager =
          StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)
      recyclerview.layoutManager = sgLayoutManager
      

      使用StaggeredGridLayoutManager會發現上下滑動過程中,經常發生圖片塊重排。根據網上說法,這是因為復用的ViewHolder和該ViewHolder要加載的圖片,它們的尺寸不一致導致。比如某個ViewHolder之前加載的圖片高度為60,之后被回收,但是尺寸信息仍然保留著,后來被一張高度80的圖片復用,由于StaggeredGridLayoutManager是根據ViewHolder的尺寸排序布局,尺寸的變化導致發生多次排序。解決方法是在ViewHolder綁定數據時(在RecyclerView.Adapter.onBindViewHolder()中),就事先設置好本次布局的最終尺寸,如下:

      override fun onBindViewHolder(holder: ThumbnailViewHolder, position: Int) {
          val layoutParams =
              holder.itemView.thumbnail_view.layoutParams as LinearLayout.LayoutParams
          //手動設置ViewHolder高度
          layoutParams.height = thumbnails[position].height
      
          Glide.with(context).load(thumbnails[position].uri)
              .into(holder.itemView.thumbnail_view)
      }
      

      當由下滑回到最頂部時,經常會出現頂部(第一行)的圖片相互重排。仔細觀察,這是因為第一行初次布局時是按順序排列而非按空缺插入,往回滑時則是按空缺(哪里最空最先排哪里),這導致順序可能與初次排序不一致。不過還好,最終仍會按照圖片尺寸各自歸位。而且這種情況只會出現在第一次由下滑回到頂部時。

      GreedoLayoutManager

      StaggeredGridLayoutManager一共有3k多行代碼,又是beta版。代碼潔癖的我把目光投向了GreedoLayoutManager,它是500px開源的一個LayoutManager,能在保持圖片寬高比例的前提下將多張圖片拼接到一行顯示,原理很簡單,看下面動圖:
      在線動圖制作brush.ninja-gif裁剪

      替換LayoutManager也相當簡單,重新設置下RecyclerView的layoutManager即可。

      val layoutManager =
          GreedoLayoutManager(adapter).also { it.setMaxRowHeight(resources.displayMetrics.heightPixels / 3) }
      recyclerview.layoutManager = layoutManager
      

      GreedoLayoutManager在布局之前需要知道item的寬高比例,只要讓Adapter實現SizeCalculatorDelegate接口即可

      override fun aspectRatioForIndex(index: Int): Double {
          val thumbnail = thumbnails[index]
          return thumbnail.width / thumbnail.height.toDouble()
      }
      

      運行界面顯示:

      可以看到每張圖片都比預期大很多,只能看到一小部分。經研究發現,上面定義的圖片展示項的布局(LinearLayout內嵌ImageView),最終呈現后,LinearLayout的尺寸是每個網格的尺寸,而內嵌的ImageView則超出了LinearLayout,似乎其最終尺寸是MeasuredSize——我們在onCreateViewHolder時使用了LayoutInflater.from(context).inflate(viewType, parent, false),這里的parent是RecyclerView,而在布局xml中寬高都設置為match_parent,因此其中ImageView的MeasuredSize同RecyclerView的寬高——然而ImageView最終尺寸應該同樣適配網格尺寸才對。

      以width為例:

      期望:ImageView.width == LinearLayout.width == 網格.width
      實際:ImageView.width == ImageView.measuredWith == RecyclerView.width
      

      我們看到每個框格其實是ImageView被截取的左上角那部分。

      經過一番搜索,網上各種對getWidthgetMeasuredWidth區別的闡述,并沒有解決我的困惑,直到這篇從源碼的角度分析,getWidth() 與 getMeasuredWidth() 的不同之處讓我知道,其實Android系統并沒有對width下定義,自定義布局時可隨意設置子項大小,是否超出屏幕也沒有限制。在我們這個場景下,估計GreedoLayoutMananger在處理了最外層控件(這里是LinearLayout)的width后,并沒有遞歸處理內部控件的width,從而導致了這個bug。

      既然如此,那么就不要外圍的LinearLayout,直接使用ImageView,反倒省了一點開銷。

      override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThumbnailViewHolder {
          return if (viewType == 0) {
              val imageView = ImageView(parent.context).apply {
                  scaleType = ImageView.ScaleType.CENTER_CROP
                  layoutParams = ViewGroup.LayoutParams(
                      ViewGroup.LayoutParams.MATCH_PARENT,
                      ViewGroup.LayoutParams.MATCH_PARENT
                  )
              }
              ThumbnailViewHolder(imageView)
          } else {
              val itemView = LayoutInflater.from(context).inflate(viewType, parent, false)
              ThumbnailViewHolder(itemView)
          }
      }
      

      當然也有ViewHolder重用導致的顯示問題,圖片只顯示一部分,且是按ViewHolder重用前的寬高比例顯示,如下:

      懶得深究,使用Glide官方文檔建議的waitForLayout()并沒有用,override(width, height)提前告知圖片尺寸解決。

      Glide.with(context)
          .load(thumbnails[position].uri)
          .override(thumbnails[position].width, thumbnails[position].height)
          .into(holder.itemView as ImageView)
      //                .waitForLayout() //并沒有用
      

      下拉刷新

      使用SwipeRefreshLayout,easy,按過不表。最后成品如下

      其它

      一般常用detachAndScrapView,RecyclerView會自動幫我們處理后續重用View[Holder]的邏輯。然而在某些場景下(如只是重排當前顯示的Views而不是移除),我們可以使用更輕量級的detachView(detach之后view就不在界面上顯示了),不過要記得在下次布局之前手動調用attachView(位置的話,detach之前在哪,attach后就在哪)或removeDetachedView/recycleView
      注意detach之后,RecyclerView.getChildCount()就相應減少。

      真正把 view layout到界面上的是RecyclerView的layoutDecorated方法。

      posted @ 2021-02-03 13:40  萊布尼茨  閱讀(1722)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 少妇高潮水多太爽了动态图 | 亚洲欧洲日产国无高清码图片| 99久久99这里只有免费费精品| 精品国产成人a在线观看| 亚洲欧美综合一区二区三区| 亚洲欧洲国产综合一区二区| 久久99九九精品久久久久蜜桃| 人人妻人人澡人人爽人人精品av| 玩弄放荡人妻少妇系列| 女人被狂躁c到高潮| 亚洲男人的天堂av手机在线观看| 成人亚洲国产精品一区不卡| 97无码人妻福利免费公开在线视频| 兰州市| 国产亚洲一在无在线观看| 亚洲中文字幕精品久久久久久动漫| 四虎影视一区二区精品| 亚洲中文字幕无码不卡电影| 三级黄色片一区二区三区| 尹人香蕉久久99天天拍| 精品国产一国产二国产三| 日本深夜福利在线观看| 久章草在线毛片视频播放| 亚洲精品欧美综合二区| 久久精品一区二区东京热| 久久久久国精品产熟女久色| 国产精品美女久久久久久麻豆| 国产女人18毛片水真多1| 国产午夜精品久久精品电影| 中文字幕久久久久人妻| 伊人久久大香线蕉av五月天| 亚洲情A成黄在线观看动漫尤物| 新婚少妇无套内谢国语播放| 日韩人妻久久精品一区二区| 国内精品伊人久久久久影院对白| 午夜福利看片在线观看| 国产一区二区av天堂热| 国产亚洲亚洲国产一二区| 野外做受三级视频| 秋霞电影院午夜无码免费视频| 亚洲av午夜福利大精品|