<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                #### **[RecyclerView](https://www.androidos.net.cn/android/5.0.1_r1/xref/frameworks/support/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java) 設計與實現(源碼解讀RecyclerView )** RecyclerView有很多內部類和接口,具體如下圖所示: :-: ![](https://box.kancloud.cn/0dc9cd0a0605e772a586e596b5599436_362x438.jpg) RecyclerView的內部類 對于RecyclerView 和ListView 來說,比較**相同的一點是使用了Adapter 和觀察者模式**,相關的代碼如下。 **RecyclerView.java** ~~~ package android.support.v7.widget; public class RecyclerView extends ViewGroup { private static final String TAG = "RecyclerView"; .............. public void setAdapter(Adapter adapter) { setAdapterInternal(adapter, false, true); requestLayout(); } private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { if (mAdapter != null) { mAdapter.unregisterAdapterDataObserver(mObserver); } if (!compatibleWithPrevious || removeAndRecycleViews) { if (mItemAnimator != null) { mItemAnimator.endAnimations(); } if (mLayout != null) { mLayout.removeAndRecycleAllViews(mRecycler); mLayout.removeAndRecycleScrapInt(mRecycler, true); } } mAdapterHelper.reset(); final Adapter oldAdapter = mAdapter; mAdapter = adapter; if (adapter != null) { //注冊觀察者 adapter.registerAdapterDataObserver(mObserver); } if (mLayout != null) { mLayout.onAdapterChanged(oldAdapter, mAdapter); } mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); //設置結構發生改變的標志位 mState.mStructureChanged = true; // 刷新視圖 markKnownViewsInvalid(); } ............ } ~~~ 在**用setAdapter時最終也會注冊一個觀察者**,這個觀察者具體實現類是RecyclerView 的內部類**RecyclerViewDataObserver**, 具體代碼如下。 **RecyclerView.RecyclerViewDataObserver** ~~~ private class RecyclerViewDataObserver extends AdapterDataObserver { @Override public void onChanged() { assertNotInLayoutOrScroll(null); if (mAdapter.hasStableIds()) { mState.mStructureChanged = true; mDataSetHasChangedAfterLayout = true; } else { mState.mStructureChanged = true; mDataSetHasChangedAfterLayout = true; } //需要重新布局 if (!mAdapterHelper.hasPendingUpdates()) { requestLayout(); } } @Override public void onItemRangeChanged(int positionStart, int itemCount) { assertNotInLayoutOrScroll(null); if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount)) { triggerUpdateProcessor(); } } @Override public void onItemRangeInserted(int positionStart, int itemCount) { assertNotInLayoutOrScroll(null); if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { triggerUpdateProcessor(); } } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { assertNotInLayoutOrScroll(null); if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { triggerUpdateProcessor(); } } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { assertNotInLayoutOrScroll(null); if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) { triggerUpdateProcessor(); } } void triggerUpdateProcessor() { if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) { ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); } else { mAdapterUpdateDuringMeasure = true; requestLayout(); } } } ~~~ **在數據集發生變化且調用了Adapter 的notifyDataSetChanged之后就會調用RecyclerViewDataObserver 的onChanged 函數,在該函數中又會調用RecyclerView 的requestLayout函數進行重新布局**。這些過程與ListView 的實現基本一致,最大的不同在于它們之間的布局實現上。**在ListView中的布局是通過自身的layoutChilden 函數來實現,而對于RecyclerView 來說它的布局職責則是交給了LayoutManager 對象**。 **RecyclerView:setLayoutManager()** ~~~ package android.support.v7.widget; public class RecyclerView extends ViewGroup { private static final String TAG = "RecyclerView"; .............. private Adapter mAdapter; private LayoutManager mLayout; ............ public void setLayoutManager(LayoutManager layout) { if (layout == mLayout) { return; } if (mLayout != null) { if (mIsAttached) { mLayout.onDetachedFromWindow(this, mRecycler); } mLayout.setRecyclerView(null); } mRecycler.clear(); mChildHelper.removeAllViewsUnfiltered(); mLayout = layout; if (layout != null) { if (layout.mRecyclerView != null) { throw new IllegalArgumentException("LayoutManager " + layout + " is already attached to a RecyclerView: " + layout.mRecyclerView); } mLayout.setRecyclerView(this); if (mIsAttached) { mLayout.onAttachedToWindow(this); } } //設置布局管理器之后重新布局 requestLayout(); } ............ @Override public void requestLayout() { if (!mEatRequestLayout) { super.requestLayout(); } else { mLayoutRequestEaten = true; } } .................. } ~~~ **在設置了布局管理器之后就會調用requestLayout 函數進行布局,然后會調用onLayout 函數** **RecyclerView:onLayout()、dispatchLayout()** ~~~ package android.support.v7.widget; public class RecyclerView extends ViewGroup { private static final String TAG = "RecyclerView"; .............. void dispatchLayout() { ............. //獲取工tern 數量 mState.mItemCount = mAdapter.getItemCount(); mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; // Step 2: Run layout mState.mInPreLayout = false; //執行布局,調用LayoutManager 的onLayoutChilden函數 mLayout.onLayoutChildren(mRecycler, mState); mState.mStructureChanged = false; mPendingSavedState = null; mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null; // 含有動畫則執行item 動畫 if (mState.mRunSimpleAnimations) { ................. } ................ } ............ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { eatRequestLayout(); //分發layout dispatchLayout(); resumeRequestLayout(false); mFirstLayoutComplete = true; } .............. } ~~~ **在onLayout 函數中最終會調用dispatchLayout 函數,而在dispatchLayout 函數中又會調用LayoutManager 的onLayoutChilden 函數進行布局**。在此,我們以LinearLayoutManager為例進行學習。下面看看[LinearLayoutManager](https://www.androidos.net.cn/android/5.0.1_r1/xref/frameworks/support/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java) 中的onLayoutChilden 函數。 **LinearLayoutManager:onLayoutChildren()、fill()** ~~~ package android.support.v7.widget; public class LinearLayoutManager extends RecyclerView.LayoutManager { private static final String TAG = "LinearLayoutManager"; ............... /** * {@inheritDoc} */ @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { .............. int startOffset; int endOffset; onAnchorReady(state, mAnchorInfo); detachAndScrapAttachedViews(recycler); mLayoutState.mIsPreLayout = state.isPreLayout(); if (mAnchorInfo.mLayoutFromEnd) { ............. } else { // 從上到下布局 updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtra = extraForEnd; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; if (mLayoutState.mAvailable > 0) { extraForStart += mLayoutState.mAvailable; } // fill towards start updateLayoutStateToFillStart(mAnchorInfo); mLayoutState.mExtra = extraForStart; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; //填充Item View fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; } .............. } .............. int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { // 存儲當前可用空間 final int start = layoutState.mAvailable; ................ // 1.計算RecyclerView 的可用布局寬或高 int remainingSpace = layoutState.mAvailable + layoutState.mExtra; LayoutChunkResult layoutChunkResult = new LayoutChunkResult(); // 2.迭代布局item view while (remainingSpace > 0 && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); //3.布局item View layoutChunk(recycler, state, layoutState, layoutChunkResult); if (layoutChunkResult.mFinished) { break; } // 4.計算布局偏移量 layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null || !state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed; // 5.計算剩余的可用空間 remainingSpace -= layoutChunkResult.mConsumed; } ................ } if (DEBUG) { validateChildOrder(); } return start - layoutState.mAvailable; } .............. } ~~~ **在onLayoutChilden 函數中會調用fill函數,在fill 函數中又會循環地調用layoutChunk 函數進行布局,每次布局完之后就會計算當前屏幕剩余的可利用空間,并且做出判斷是否還需要布局ItemView**。因此,先看看layoutChunk的實現。 **LinearLayoutManager:layoutChunk()** ~~~ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { // 1.獲取Item View View view = layoutState.next(recycler); ............... //2.獲取Item View的布局參數 RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); // 3.丈量Item View measureChildWithMargins(view, 0, 0); // 4.計算該Item View消耗的寬度或高度 result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); // Item View的上下左右坐標位置 int left, top, right, bottom; // 5.按照水平或豎直方向布局,計算Item View 的上下左右坐標 if (mOrientation == VERTICAL) { if (isLayoutRTL()) { right = getWidth() - getPaddingRight(); left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); } else { left = getPaddingLeft(); right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); } if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { bottom = layoutState.mOffset; top = layoutState.mOffset - result.mConsumed; } else { top = layoutState.mOffset; bottom = layoutState.mOffset + result.mConsumed; } } else { //豎直方向布局的計算方式 } // 6 . 布局item view layoutDecorated(view, left + params.leftMargin, top + params.topMargin, right - params.rightMargin, bottom - params.bottomMargin); if (DEBUG) { Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)); } if (params.isItemRemoved() || params.isItemChanged()) { result.mIgnoreConsumed = true; } result.mFocusable = view.isFocusable(); } ~~~ **在layoutChunk 中首先從layoutState 中獲取到Item View ,然后獲取Item View 的布局參數、尺寸信息,并且根據布局方式(橫向或者縱向)計算出Item View的上下左右坐標,最后調用layoutDecorated 函數實現布局**。 layoutDecorated 函數定義在LayoutManager 中,具體代碼如下。 **RecyclerView.LayoutManager:layoutDecorated()** ~~~ public void layoutDecorated(View child, int left, int top, int right, int bottom) { final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; child.layout(left + insets.left, top + insets.top, right - insets.right, bottom - insets.bottom); } ~~~ **從上述程序可以看到, 只是調用了Item View 的layout函數( child.layout函數)將Item View 布局到具體的位置。這樣一來,就將布局的職責從RecyclerView 分離到LayoutManager 中,也使得RecyclerView 更為靈活。** 這里的**layoutChunk 函數很重要**,**首先通過LayoutState 對象的next 函數獲取到Item View**,這里也是一個重要的地方。我們看看LayoutState 函數的next 實現。 **LinearLayoutManager.LayoutState:next()** ~~~ package android.support.v7.widget; public class LinearLayoutManager extends RecyclerView.LayoutManager { private static final String TAG = "LinearLayoutManager"; ............... static class LayoutState { .............. View next(RecyclerView.Recycler recycler) { if (mScrapList != null) { return nextFromLimitedList(); } //調用Recycler中的getViewForPosition獲取item View final View view = recycler.getViewForPosition(mCurrentPosition); mCurrentPosition += mItemDirection; return view; } ............. } .............. } ~~~ **實際上就是調用RecyclerView.Recycler 對象getViewForPosition 函數獲取到Item View** ,我們繼續深RecyclerView.Recycler 類的相關代碼。 **RecyclerView.Recycler:getViewForPosition()、getChangedScrapViewForPosition()、getScrapViewForPosition()** ~~~ package android.support.v7.widget; public class RecyclerView extends ViewGroup { private static final String TAG = "RecyclerView"; .............. public final class Recycler { final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>(); private ArrayList<ViewHolder> mChangedScrap = null; final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); ................ //根據position 獲取該位置對應的View public View getViewForPosition(int position) { return getViewForPosition(position, false); } View getViewForPosition(int position, boolean dryRun) { if (position < 0 || position >= mState.getItemCount()) { throw new IndexOutOfBoundsException("Invalid item position " + position + "(" + position + "). Item count:" + mState.getItemCount()); } boolean fromScrap = false; ViewHolder holder = null; // 1.從mChangedScrap中獲取ViewHolder緩存 if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrap = holder != null; } // 2.從mAttachedScrap中獲取ViewHolder緩存 if (holder == null) { holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); .................. } if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); ................. //從其他ViewHolder緩存中檢測是否有緩存,代碼省略 // 3.沒有ViewHolder,則需要創建ViewHolder,這里會調用onCreateViewHolder 函數 if (holder == null) { holder = mAdapter.createViewHolder(RecyclerView.this, mAdapter.getItemViewType(offsetPosition)); if (DEBUG) { Log.d(TAG, "getViewForPosition created new ViewHolder"); } } } boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { if (DEBUG && holder.isRemoved()) { throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder); } final int offsetPosition = mAdapterHelper.findPositionOffset(position); // 4.綁定數據,這里會調用Adapter的onBindViewHolder mAdapter.bindViewHolder(holder, offsetPosition); attachAccessibilityDelegate(holder.itemView); bound = true; if (mState.isPreLayout()) { holder.mPreLayoutPosition = position; } } // 設置Item View 的LayoutParams final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; if (lp == null) { rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); holder.itemView.setLayoutParams(rvLayoutParams); } else if (!checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); } else { rvLayoutParams = (LayoutParams) lp; } rvLayoutParams.mViewHolder = holder; rvLayoutParams.mPendingInvalidate = fromScrap && bound; // 5.返回itemView return holder.itemView; } ................ ViewHolder getChangedScrapViewForPosition(int position) { // If pre-layout, check the changed scrap for an exact match. final int changedScrapSize; if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) { return null; } // find by position for (int i = 0; i < changedScrapSize; i++) { final ViewHolder holder = mChangedScrap.get(i); if (!holder.wasReturnedFromScrap() && holder.getPosition() == position) { holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); return holder; } } // find by id if (mAdapter.hasStableIds()) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) { final long id = mAdapter.getItemId(offsetPosition); for (int i = 0; i < changedScrapSize; i++) { final ViewHolder holder = mChangedScrap.get(i); if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) { holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); return holder; } } } } return null; } ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) { final int scrapCount = mAttachedScrap.size(); // Try first for an exact, non-invalid match from scrap. for (int i = 0; i < scrapCount; i++) { final ViewHolder holder = mAttachedScrap.get(i); if (!holder.wasReturnedFromScrap() && holder.getPosition() == position && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { if (type != INVALID_TYPE && holder.getItemViewType() != type) { Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" + " wrong view type! (found " + holder.getItemViewType() + " but expected " + type + ")"); break; } holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); return holder; } } if (!dryRun) { View view = mChildHelper.findHiddenNonRemovedView(position, type); if (view != null) { // ending the animation should cause it to get recycled before we reuse it mItemAnimator.endAnimation(getChildViewHolder(view)); } } // Search in our first-level recycled view cache. final int cacheSize = mCachedViews.size(); for (int i = 0; i < cacheSize; i++) { final ViewHolder holder = mCachedViews.get(i); if (!holder.isInvalid() && holder.getPosition() == position) { if (!dryRun) { mCachedViews.remove(i); } if (DEBUG) { Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type + ") found match in cache: " + holder); } return holder; } } return null; } .............. } .............. } ~~~ 我們知道在**RecyclerView的Adapter 中被緩存的單位已經不是Item View 了, 而是一個View Holder** , 而原來的ListView 則是緩存的View 。在**RecyclerView. Recycler 類中有mAttachedScrap、mChangedScrap、mCachedViews 幾個ViewHolder 列表對象**,它們就是**用于緩存ViewHolder** 的。 **在通過LayoutState 的next 函數獲取Item View 時, 實際上調用的是RecyclerView.Recycler的getViewForPosition函數**, **該函數首先會從這幾個ViewHolder 緩存中獲取對應位置的ViewHolder,如果沒有緩存則調用RecyclerView.Adapter中的createView Holder 函數來創建一個新的ViewHolder**,我們看看RecyclerView.Adapter 的createViewHolder 函數。 **RecyclerView.Adapter:createViewHolder()** ~~~ package android.support.v7.widget; public class RecyclerView extends ViewGroup { private static final String TAG = "RecyclerView"; .............. public static abstract class Adapter<VH extends ViewHolder> { private final AdapterDataObservable mObservable = new AdapterDataObservable(); private boolean mHasStableIds = false; ............ public final VH createViewHolder(ViewGroup parent, int viewType) { // 調用onCreateViewHolder 創建ViewHolder,用戶需要覆寫onCreateViewHolder函數 final VH holder = onCreateViewHolder(parent, viewType); holder.mItemViewType = viewType; return holder; } //創建ViewHolder,子類需覆寫 public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); } .............. } ~~~ 在**createViewHolder 函數中實際上調用了onCreateViewHolder 函數創建ViewHolder 對象。這也就是為什么在繼承RecyclerView.Adapter 時,需要覆寫onCreateViewHolder 函數, 并且在該函數中返回ViewHolder 的原因**。 **通過這個onCreateViewHolder 函數會加載Item View視圖, 并且把Item View 當作ViewHolder 的構造參數傳遞給ViewHolder(這個自定義的ViewHolder繼承于RecyclerView.ViewHolder),此時ViewHolder 就構建完畢了**。調用了Adapter 的createViewHolder 后,此時執行到Recycler 的getViewForPosition 函數的注釋4 處,也就是調用了Adapter 中的bindViewHolder 函數, 相關代碼如下。 **RecyclerView.Adapter:bindViewHolder()** ~~~ public final void bindViewHolder(VH holder, int position) { holder.mPosition = position; if (hasStableIds()) { holder.mItemId = getItemId(position); } //調用onBindViewHolder綁定數據 onBindViewHolder(holder, position); //設置holder 的flags holder.setFlags(ViewHolder.FLAG_BOUND, ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); } //綁定數據, 子類需覆寫 public abstract void onBindViewHolder(VH holder, int position); ~~~ bindViewHolder 函數與createViewHolder 如出一轍,只是它調用的是onBindViewHolder 函數。與onCreateViewHolder 一樣, **onBindViewHolder 也需要子類覆寫, 并且在這個函數中進行數據綁定**。在getViewForPosition 中實際上相當于一個模板方法,它封裝了獲取、綁定ViewHolder 的過程,子類只需要覆寫特定的函數即可完成這個過程。 執行完onBindViewHolder 函數之后數據就被綁定到Item View 上了。 我們最后再來分析一下這個過程。 與ListView 一樣, **RecyclerView 還是通過Adapter 和觀察者模式進行數據綁定,使得Recycler View 的靈活性得到保證**。**RecyclerView 的Adapter 并不是ListView 中的Adapter,它封裝了ViewHolder 的創建與綁定等邏輯,降低了用戶的使用成本。RecyclerView 也將緩存單元從Item View 換成了ViewHolder,在一定程度上建立起了規范**。 RecyclerView 與ListView **最大的不同是RecyclerView 將布局的工作交給了LayoutManager,在LayoutManager 的onLayou.tChilden 中對Item View 進行布局、執行動畫等操作**,這樣一來,**使得RecyclerView 可以動態地替換掉布局方式**,例如,在運行時給RecyclerView 重新設置一個LayoutManager 就可以將原來是線性布局的視圖改成網格布局,這大大地增加了靈活性。將布局職責獨立出來也符合單一職責原則,而使用組合代替繼承也會減少精合、增強健壯性,也使得RecyclerView 的布局具有更好的擴展性。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看