国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

中山專業(yè)制作網(wǎng)站武漢網(wǎng)絡(luò)推廣自然排名

中山專業(yè)制作網(wǎng)站,武漢網(wǎng)絡(luò)推廣自然排名,長沙網(wǎng)絡(luò)公司推廣,京山大洪山旅游開發(fā)有限公司 做網(wǎng)站此前大部分涉及到 RecyclerView 頁面的 LayoutManager基本上用系統(tǒng)提供的 LinearLayoutManager 、GridLayoutManager 就能解決,但在一些特殊場景上還是需要我們自定義 LayoutManager。之前基本上沒有自己寫過,在網(wǎng)上看各種源碼各種文章,剛開始…

此前大部分涉及到 RecyclerView 頁面的 LayoutManager基本上用系統(tǒng)提供的 LinearLayoutManager 、GridLayoutManager 就能解決,但在一些特殊場景上還是需要我們自定義 LayoutManager。之前基本上沒有自己寫過,在網(wǎng)上看各種源碼各種文章,剛開始花了好多時(shí)間去理解整體流程,因?yàn)樗鼈兌冀o我一種非常非常復(fù)雜的感覺,包括相關(guān)的博客文章也是。經(jīng)過一段時(shí)間摸索,也慢慢能理解為什么要那么復(fù)雜了,這的確不是特別容易入門。所以對整體的流程進(jìn)行了一個(gè)拆解,盡量原子化一點(diǎn),對自己學(xué)習(xí)的一個(gè)總結(jié),也希望能幫助到一部分人能對 LayoutManager 入門。

本文最終實(shí)現(xiàn)一個(gè)簡單的 LinearLayoutManager(只支持 VERTICAL)方向,適合對 LayoutManager 整體流程的學(xué)習(xí)與理解,整體代碼分為多個(gè)文件,每個(gè)文件都是對前一段代碼的補(bǔ)充,方便理解,整體項(xiàng)目源碼已提交 Github: LayoutManagerGradually,代碼里面寫了很多很多注釋,如果不想浪費(fèi)時(shí)間,可以直接看代碼運(yùn)行,跳過這篇文章,把每一個(gè) LayoutManager 都跑一下體驗(yàn)結(jié)合代碼看看。

自定義 LayoutManager 的必要元素

  • 繼承 RecyclerView.LayoutManager 并實(shí)現(xiàn) generateDefaultLayoutParams() 方法

  • 重寫onLayoutChildren 第一次數(shù)據(jù)填充的時(shí)候數(shù)據(jù)添加

  • 重寫 canScrollHorizontally()canScrollVertically()方法設(shè)定支持滑動(dòng)的方向

  • 重寫 scrollHorizontallyBy()scrollVerticallyBy()方法,在滑動(dòng)的時(shí)候?qū)ζ聊灰酝獾?View 進(jìn)行回收,以及填充即將滑動(dòng)進(jìn)入屏幕范圍內(nèi)的 View 進(jìn)行填充

  • 重寫 scrollToPosition()smoothScrollToPosition()方法支持

其中onLayoutChildrenscrollHorizontallyBy/scrollVerticallyBy 是最核心且最復(fù)雜的方法,這里稍微拎出來講一下

onLayoutChildren

這個(gè)方法類似于自定義 ViewGroup 的 onLayout() 方法,RecyclerView 的 LayoutManager.onLayoutChildren 在以下幾個(gè)時(shí)機(jī)會(huì)被觸發(fā):

  • 當(dāng) RecyclerView 首次附加到窗口時(shí)
  • 當(dāng)Adapter 的數(shù)據(jù)集發(fā)生變化
  • 當(dāng) RecyclerView 被 執(zhí)行 RequetLayout的時(shí)候
  • 當(dāng) LayoutManager 發(fā)生變化時(shí)

scrollHorizontallyBy/scrollVerticallyBy

方法的主要作用包括:

  1. 更新 ItemView 的位置:根據(jù)傳入的垂直滾動(dòng)距離(dy 參數(shù)),更新子視圖在屏幕上的位置。通常調(diào)用 offsetChildrenVertical 方法。

  2. 回收不可見的 ItemView:在滾動(dòng)過程中,一些 ItemView 可能會(huì)離開屏幕,變得不可見。scrollVerticallyBy 方法需要負(fù)責(zé)回收這些子視圖并將它們放入回收池,以便稍后復(fù)用。

  3. 添加新的 ItemView:在滾動(dòng)過程中,新的 ItemView 可能需要顯示在屏幕上。scrollVerticallyBy 方法需要從回收池中獲取可復(fù)用的視圖并將它們添加到屏幕上。這通常涉及到調(diào)用 RecyclerView.RecyclergetViewForPosition 方法。

  4. 返回實(shí)際滾動(dòng)距離:由于 ItemView 的數(shù)量有限,滾動(dòng)可能會(huì)受到限制。例如,當(dāng)滾動(dòng)到列表頂部或底部時(shí),滾動(dòng)可能會(huì)停止。在這種情況下,實(shí)際滾動(dòng)的距離可能會(huì)小于傳入的 dy 參數(shù)。scrollVerticallyBy 方法需要返回實(shí)際滾動(dòng)的距離,以便 RecyclerView 可以正確地更新滾動(dòng)條和觸發(fā)滾動(dòng)事件。

概念就簡單講這么多, talk is cheap show me the code,直接看代碼理解會(huì)比較深刻

逐步實(shí)現(xiàn)

要實(shí)現(xiàn)一個(gè)可用的 LayoutManger 通常我們需要實(shí)現(xiàn)以下流程

  • 數(shù)據(jù)填充并且只需要填充屏幕范圍內(nèi)的 ItemView
  • 回收掉屏幕以外的 ItemView
  • 屏幕外 ItemView 再回到屏幕后,需要重新填充
  • 對滑動(dòng)邊界邊界進(jìn)行處理
  • 對 scrollToPosition 和 smoothScrollToPosition進(jìn)行支持

我們不用一上來就實(shí)現(xiàn)最終的效果,而是一步一步來,看看 LayoutManger 是怎么漸漸地變化,最終能跑起來的。

0 最簡單的 LayoutManager

代碼查看:MostSimpleLayoutManager,我們關(guān)注 onLayoutChildren 方法:

override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State?) {// 垂直方向的偏移量var offsetTop = 0// 實(shí)際業(yè)務(wù)中最好不要這樣一次性加載所有的數(shù)據(jù),這里只是最簡單地演示一下整體是如何工作的for (itemIndex in 0 until itemCount) {// 從適配器獲取與給定位置關(guān)聯(lián)的視圖val itemView = recycler.getViewForPosition(itemIndex)// 將視圖添加到 RecyclerView 中addView(itemView)// 測量并布局視圖measureChildWithMargins(itemView, 0, 0)// 拿到寬高(包括ItemDecoration)val width = getDecoratedMeasuredWidth(itemView)val height = getDecoratedMeasuredHeight(itemView)// 對要添加的子 View 進(jìn)行布局layoutDecorated(itemView, 0, offsetTop, width, offsetTop + height)offsetTop += height}
}

上面的代碼主要演示了,如何利用addView layoutDecorated等方法,將 ItemView 添加到 RecyclerView 上。代碼可見是 將所有的 ItemView(即使它在屏幕上不可見)一次性全部加載到了 RecyclerView上, 這里一般不這么做,只是這里這里只是最簡單地演示一下整體是如何工作的。

運(yùn)行在手機(jī)上能看到這樣的效果:Item數(shù)據(jù)已經(jīng)被全部添加到界面上了,并且各個(gè)方向的滑動(dòng)都支持。

1 更合理的數(shù)據(jù)添加方式

代碼查看:LinearLayoutManager1.kt

對最開始的代碼進(jìn)行優(yōu)化,只在屏幕范圍內(nèi)的區(qū)域進(jìn)行數(shù)據(jù)的添加,這樣就不需要一次性將所有數(shù)據(jù)就添加上去,如果 Adapter 的 ItemCount 足夠巨大,for all addView 的話,很容易就 OOM。

override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {// 垂直方向上的的空間大小var remainSpace = height - paddingTop//垂直方向的偏移量var offsetTop = 0var currentPosition = 0while (remainSpace > 0 && currentPosition < state.itemCount) {// 從適配器獲取與給定位置關(guān)聯(lián)的視圖val itemView = recycler.getViewForPosition(currentPosition)// 將視圖添加到 RecyclerView 中addView(itemView)// 測量并布局視圖measureChildWithMargins(itemView, 0, 0)// 拿到寬高(包括ItemDecoration)val itemWidth = getDecoratedMeasuredWidth(itemView)val itemHeight = getDecoratedMeasuredHeight(itemView)// 對要添加的子 View 進(jìn)行布局layoutDecorated(itemView, 0, offsetTop, itemWidth, offsetTop + itemHeight)offsetTop += itemHeightcurrentPosition++// 可用空間減少remainSpace -= itemHeight}
}

2 對屏幕外的View回收

代碼查看:LinearLayoutManager2

RecylerView 沒有 recycler 怎么行呢?當(dāng) RecylerView 的 ItemView 滑出屏幕后我們需要對齊進(jìn)行回收,實(shí)現(xiàn)的話需要在 scrollVerticallyBy中,比較復(fù)雜的邏輯就是怎么去判斷:ItemView 在屏幕以外,最后利用:removeAndRecycleView方法進(jìn)行回收

override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State?): Int {// 在這里處理上下的滾動(dòng)邏輯,dy 表示滾動(dòng)的距離// 平移所有子視圖offsetChildrenVertical(-dy)// 如果實(shí)際滾動(dòng)距離與 dy 相同,返回 dy;如果未滾動(dòng),返回 0recycleInvisibleView(dy, recycler)return dy
}/*** 回收掉在界面上看不到的 ItemView** @param dy* @param recycler*/
private fun recycleInvisibleView(dy: Int, recycler: RecyclerView.Recycler) {val totalSpace = orientationHelper.totalSpace// 將要回收View的集合val recycleViews = hashSetOf<View>()// 從下往上滑if (dy > 0) {for (i in 0 until childCount) {val child = getChildAt(i)!!// 從下往上滑從最上面的 item 開始計(jì)算val top = getDecoratedTop(child)// 判斷最頂部的 item 是否已經(jīng)完全不可見,如何可見,那說明底下的 item 也是可見val height = top - getDecoratedBottom(child)if (height - top < 0) {break}recycleViews.add(child)}} else if (dy < 0) {   // 從上往下滑for (i in childCount - 1 downTo 0) {val child = getChildAt(i)!!// 從上往下滑從最底部的 item 開始計(jì)算val bottom = getDecoratedBottom(child)// 判斷最底部的 item 是否已經(jīng)完全不可見,如何可見,那說明上面的 item 也是可見val height = bottom - getDecoratedTop(child)if (bottom - totalSpace < height) {break}recycleViews.add(child)}}// 真正把 View 移除掉的邏輯for (view in recycleViews) {// [removeAndRecycleView]// 用于從視圖層次結(jié)構(gòu)中刪除某個(gè)視圖,并將其資源回收,以便在需要時(shí)重新利用removeAndRecycleView(view, recycler)}recycleViews.clear()
}

運(yùn)行在手機(jī)上能看到這樣的效果:滑出屏幕外的ItemView 被回收掉了

3 向上滑動(dòng)的時(shí)View的填充

代碼查看:LinearLayoutManager3

override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State?): Int {// 填充 viewfillView(dy, recycler)// 移動(dòng) viewoffsetChildrenVertical(-dy)// 回收 ViewrecycleInvisibleView(dy, recycler)return dy
}/*** 填充重新進(jìn)入屏幕內(nèi)的 ItemView*     getChildCount():childCount-> 當(dāng)前屏幕內(nèi)RecyclerView展示的 ItemView 數(shù)量*     getItemCount():itemCount-> 最大的 ItemView 數(shù)量,也就是 Adapter 傳遞的數(shù)據(jù)的數(shù)量*/
private fun fillView(dy: Int, recycler: RecyclerView.Recycler) {val verticalSpace = orientationVerticalHelper.totalSpacevar remainSpace = 0var nextFillPosition = 0//垂直方向的偏移量var offsetTop = 0var offsetLeft = 0// 從下往上滑,那么需要向底部添加數(shù)據(jù)if (dy > 0) {val anchorView = getChildAt(childCount - 1) ?: returnval anchorPosition = getPosition(anchorView)val anchorBottom = getDecoratedBottom(anchorView)val anchorLeft = getDecoratedLeft(anchorView)remainSpace = verticalSpace - anchorBottom// 垂直可用的數(shù)據(jù)為<0,意外著這時(shí)候屏幕底部的位置剛好在最底部的 ItemView 上,還需要向上滑動(dòng)一點(diǎn)點(diǎn)...我們才能添加 Viewif (remainSpace < 0) {return}nextFillPosition = anchorPosition + 1offsetTop = anchorBottomoffsetLeft = anchorLeftif (nextFillPosition >= itemCount) {return}} else if (dy < 0) {  // 從上往下滑,那么需要向頂部添加數(shù)據(jù)//no-op 暫時(shí)不實(shí)現(xiàn)從上往下滑的底部數(shù)據(jù)填充}while (remainSpace > 0 && nextFillPosition < itemCount) {// 從適配器獲取與給定位置關(guān)聯(lián)的視圖val itemView = recycler.getViewForPosition(nextFillPosition)// 將視圖添加到 RecyclerView 中addView(itemView)// 測量并布局視圖measureChildWithMargins(itemView, 0, 0)// 拿到寬高(包括ItemDecoration)val itemWidth = getDecoratedMeasuredWidth(itemView)val itemHeight = getDecoratedMeasuredHeight(itemView)// 對要添加的子 View 進(jìn)行布局,相比onLayoutChildren 里面的實(shí)現(xiàn)添加了:offsetLeft(因?yàn)槲覀儧]有禁止掉 左右的滑動(dòng))// 試著把 offsetLeft 改成0,也就是最原始的樣子,然后左右上下滑滑,你會(huì)有意外收獲layoutDecorated(itemView, offsetLeft, offsetTop, itemWidth + offsetLeft, offsetTop + itemHeight)offsetTop += itemHeightnextFillPosition++// 可用空間減少remainSpace -= itemHeight}
}

運(yùn)行在手機(jī)上能看到這樣的效果:向上滑動(dòng)的時(shí)候,底部陸續(xù)有元素填充,但向下滑動(dòng)的時(shí)候沒有填充數(shù)據(jù)

4 兩個(gè)方向的View填充

代碼查看:LinearLayoutManager4

補(bǔ)齊從上往下滑之后添加的邏輯

private fun fillView(dy: Int, recycler: RecyclerView.Recycler) {val verticalSpace = orientationVerticalHelper.totalSpacevar remainSpace = 0var nextFillPosition = 0//垂直方向的偏移量var offsetTop = 0var offsetLeft = 0// 從下往上滑,那么需要向底部添加數(shù)據(jù)if (dy > 0) {……} else if (dy < 0) {  // 從上往下滑,那么需要向頂部添加數(shù)據(jù)val anchorView = getChildAt(0) ?: returnval anchorPosition = getPosition(anchorView)val anchorTop = getDecoratedTop(anchorView)offsetLeft = getDecoratedLeft(anchorView)remainSpace = anchorTop// 垂直可用的數(shù)據(jù)為<0,意外著這時(shí)候屏幕頂部的位置剛好在最底部的 ItemView 上,還需要向下滑動(dòng)一點(diǎn)點(diǎn)...我們才能添加 Viewif (anchorTop < 0) {return}nextFillPosition = anchorPosition - 1if (nextFillPosition < 0) {return}val itemHeight = getDecoratedMeasuredHeight(anchorView)// 新的布局的itemView 的頂部位置應(yīng)該以 anchorTop - itemHeight 開始offsetTop = anchorTop - itemHeight}while (remainSpace > 0 &&((nextFillPosition < itemCount) && (nextFillPosition >= 0))) {// 從適配器獲取與給定位置關(guān)聯(lián)的視圖val itemView = recycler.getViewForPosition(nextFillPosition)// 將視圖添加到 RecyclerView 中k,從頂部添加的話,需要加到最前的位置if (dy > 0) {addView(itemView)} else {addView(itemView, 0)}……if (dy > 0) {offsetTop += itemHeightnextFillPosition++} else {offsetTop -= itemHeightnextFillPosition--}// 可用空間減少remainSpace -= itemHeight}

運(yùn)行在手機(jī)上能看到這樣的效果:向上或者滑動(dòng)的時(shí)候,底部陸續(xù)都有元素填充

5 對頂部和底部滑動(dòng)邊界處理

代碼查看:LinearLayoutManager5

對于前面的實(shí)現(xiàn)會(huì)發(fā)現(xiàn)會(huì):不停地下滑或者上滑會(huì)留出來巨大的空白。這里對填充 View 的邏輯進(jìn)行改造,需要進(jìn)行邊界檢測。

override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State?): Int {// 填充 viewval adjustedDy = fillView(dy, recycler)// 移動(dòng) viewoffsetChildrenVertical(-adjustedDy)// 回收 ViewrecycleInvisibleView(adjustedDy, recycler)// 由于需要對邊界進(jìn)行限制,所以需要對原始的 dy 進(jìn)行修正,這里不再直接返回 dyreturn adjustedDy
}

這里的整體注釋我寫在了代碼里面,可以看圖稍微理解一下,以向上滑動(dòng)為例:假設(shè)這一次滑動(dòng)的距離非常非常大(想象成10000像素),如果直接滑動(dòng)的話,我們有50個(gè)元素,每個(gè)元素高度100像素,最大高度也只有50x100=5000,那么滑動(dòng)后一定會(huì)留下大量空區(qū)域。需要對當(dāng)前傳入的這 10000 像素做調(diào)整:只給到可滑動(dòng)的最大距離,如果不能滑動(dòng)了就返回0。

運(yùn)行在手機(jī)上能看到這樣的效果:向上或者滑動(dòng)的時(shí)候,達(dá)到最大的位置時(shí)候是不能再滑動(dòng)的。

6 實(shí)現(xiàn) scrollToPosition

代碼查看:LinearLayoutManager6

到這里這個(gè) LinearLayoutManager 看著已經(jīng)能正常運(yùn)行了,但一般還需要支持scrollToPositionsmoothScrollToPositio

private var mPendingScrollPosition = RecyclerView.NO_POSITIONoverride fun scrollToPosition(position: Int) {super.scrollToPosition(position)if (position < 0 || position >= itemCount) {return}mPendingScrollPosition = positionrequestLayout()
}override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {……var currentPosition = 0if (mPendingScrollPosition != RecyclerView.NO_POSITION) {currentPosition = mPendingScrollPosition}while (remainSpace > 0 && currentPosition < state.itemCount) {…… // 填充View 的邏輯}
}

scrollToPosition 的實(shí)現(xiàn)比較簡單,如上代碼所示:在 scrollToPosition 的時(shí)候記錄一次目標(biāo)position,再 requestLayout 一波,還記得之前有提到過:onLayoutChildren 會(huì)在 requestLayout 的時(shí)候調(diào)用一次,于是再將onLayoutChildren邏輯改寫,不再從第0個(gè)元素開始,而是從目標(biāo)位置進(jìn)行布局。

運(yùn)行在手機(jī)上能看到這樣的效果:點(diǎn)擊 scrollTo30 將會(huì)滑動(dòng)到 第30個(gè)位置。

7 實(shí)現(xiàn) smoothScrollToPosition

代碼查看:LinearLayoutManager7

要實(shí)現(xiàn)自定義的 smoothScrollToPosition 動(dòng)畫效果,這一塊如果要完全自己實(shí)現(xiàn)的話比較復(fù)雜,可以直接使用系統(tǒng)提供的 LinearSmoothScroller改造,也可以繼承 RecyclerView.SmoothScroller 自定義,也可以完全不使用 SmoothScroller, 照著 SmoothScroller 的實(shí)現(xiàn)使用類似 ValueAnimator 自定義動(dòng)畫,添加動(dòng)畫 UpdateListener,在 onAnimationUpdate 的時(shí)候動(dòng)態(tài)計(jì)算布局從而實(shí)現(xiàn)滑動(dòng)動(dòng)畫,這里拿 LinearSmoothScroller 舉例:

override fun smoothScrollToPosition(recyclerView: RecyclerView,state: RecyclerView.State,position: Int
) {if (position >= itemCount || position < 0) {return}val scroller: LinearSmoothScroller = object : LinearSmoothScroller(recyclerView.context) {/*** 這個(gè)方法用于計(jì)算滾動(dòng)到目標(biāo)位置所需的滾動(dòng)向量。滾動(dòng)向量是一個(gè)二維向量,包含水平和垂直方向上的滾動(dòng)距離** @param targetPosition 滑動(dòng)的目標(biāo)位置* @return  返回一個(gè) PointF 對象,表示滾動(dòng)向量。*              PointF.x 表示水平方向上的滾動(dòng)距離,*              PointF.y 表示垂直方向上的滾動(dòng)距離*/override fun computeScrollVectorForPosition(targetPosition: Int): PointF {// 查找到屏幕里顯示的第 1 個(gè)元素與val firstChildPos = getPosition(getChildAt(0)!!)val direction = if (targetPosition < firstChildPos) -1 else 1// x 左右滑動(dòng),由于我們只實(shí)現(xiàn)了垂直的滑動(dòng),所以 x方向?yàn)?即可// 整數(shù)代表正向移動(dòng),負(fù)數(shù)代表反向移動(dòng),這里的數(shù)值大小不重要,源碼里面最終都會(huì) normalize 歸一化處理return PointF(0f, direction.toFloat())}/*** 計(jì)算每像素速度** @param displayMetrics* @return 返回每一像素的耗時(shí),單位ms,假設(shè)返回值是1.0 代表著:1ms 內(nèi)會(huì)滑動(dòng) 1像素,1s會(huì)滑動(dòng)1000像素*/override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?): Float {return super.calculateSpeedPerPixel(displayMetrics)}/*** 滑動(dòng)速度的插值(實(shí)現(xiàn)滑動(dòng)速度隨著滑動(dòng)時(shí)間的變化)** @param dx* @return*/override fun calculateTimeForDeceleration(dx: Int): Int {return super.calculateTimeForDeceleration(dx)}// 很多方法可以使用,不再一一列舉// ...}scroller.targetPosition = position// 執(zhí)行默認(rèn)動(dòng)畫的邏輯startSmoothScroll(scroller)
}

運(yùn)行在手機(jī)上能看到這樣的效果:點(diǎn)擊 smoothScrollTo30 將會(huì)有個(gè)動(dòng)畫效果滑動(dòng)到第30個(gè)位置。

以上基本上一個(gè)自定義 LayoutManager 的雛形就已經(jīng)完成了,雖然只實(shí)現(xiàn)了一個(gè)方向的滑動(dòng),但是其原理都是一樣的,剩下的就是各種細(xì)節(jié)的打磨了,可以加各種自己想要的效果,比如:指定位置 放大一定的系數(shù),或者更炫酷的滑動(dòng)動(dòng)畫…

總結(jié)

本文主要整理了自定義 LayoutManager 的必要元素,以及其核心方法 scrollHorizontallyBy/scrollVerticallyBy、onLayoutChildren 的作用與調(diào)用時(shí)機(jī),接下對實(shí)現(xiàn)一個(gè)簡單的 LinearLayoutManger 進(jìn)行邏輯拆解,從最簡單的不滑動(dòng)回收和填充以及不含滑動(dòng)邊界檢測,到最終一個(gè)具備基本功能的 LinearLayoutManger

源碼:https://github.com/VomPom/LayoutManagerGradually

參考:

《看完這篇文章你還不會(huì)自定義LayoutManager,我吃X!》

《/LayoutManager分析與實(shí)踐》

Building a RecyclerView LayoutManager – Part 1

http://m.aloenet.com.cn/news/34245.html

相關(guān)文章:

  • 彭陽門戶網(wǎng)站建設(shè)網(wǎng)絡(luò)推廣的方式和途徑有哪些
  • 做網(wǎng)站搞個(gè)物理服務(wù)器引流推廣犯法嗎
  • 網(wǎng)站沒有問題但是一直做不上首頁seo托管
  • 網(wǎng)站程序設(shè)計(jì)百度鏈接收錄提交入口
  • 學(xué)做效果圖的網(wǎng)站有哪些新手電商運(yùn)營從哪開始學(xué)
  • 網(wǎng)站底部樣式智能建站平臺(tái)
  • 網(wǎng)站開發(fā)技術(shù)項(xiàng)目代碼搜索南寧seo外包要求
  • 做網(wǎng)站業(yè)務(wù)員怎么樣為企業(yè)策劃一次網(wǎng)絡(luò)營銷活動(dòng)
  • 怎么做新網(wǎng)站的推廣下載優(yōu)化大師并安裝
  • 正規(guī)網(wǎng)站建設(shè)官網(wǎng)上海做網(wǎng)絡(luò)口碑優(yōu)化的公司
  • 電商網(wǎng)絡(luò)運(yùn)營浙江搜索引擎優(yōu)化
  • 鄭州企業(yè)網(wǎng)站優(yōu)化哪家便宜2022適合小學(xué)生的簡短新聞
  • 山西網(wǎng)站制作公司百度小說官網(wǎng)
  • 照片做視頻ppt模板下載網(wǎng)站好seo關(guān)鍵詞排名優(yōu)化價(jià)格
  • 做電影網(wǎng)站涉及的侵權(quán)問題網(wǎng)盤搜索神器
  • 做釣魚網(wǎng)站要什么工具中企動(dòng)力做網(wǎng)站推廣靠譜嗎
  • wap網(wǎng)站微信一鍵登錄網(wǎng)絡(luò)營銷案例有哪些
  • 公司網(wǎng)站背景圖百度快速收錄教程
  • 網(wǎng)站建設(shè)明細(xì)報(bào)價(jià)單凡科網(wǎng)免費(fèi)建站
  • 深圳做網(wǎng)站排名價(jià)格百度網(wǎng)盤怎么找片
  • 做ppt一般在什么網(wǎng)站打開網(wǎng)址資料網(wǎng)站
  • 網(wǎng)站后臺(tái)登陸地址網(wǎng)站如何推廣
  • 購物網(wǎng)站開發(fā)軟件百度網(wǎng)盤官方網(wǎng)站
  • 每天網(wǎng)站外鏈做幾條最好產(chǎn)品怎么做市場推廣
  • 公司網(wǎng)站服務(wù)器維護(hù)營銷推廣活動(dòng)方案
  • 網(wǎng)站怎么做導(dǎo)航條手機(jī)百度最新正版下載
  • 網(wǎng)站建設(shè)流程機(jī)構(gòu)互聯(lián)網(wǎng)廣告聯(lián)盟
  • 網(wǎng)站建設(shè)市場趨勢深圳百度推廣關(guān)鍵詞推廣
  • 山東省城鄉(xiāng)和住房建設(shè)廳網(wǎng)站廣東疫情最新消息今天又封了
  • 茶葉網(wǎng)站建設(shè)要求百度的營銷策略