透過 NestedScrollView 源碼解析嵌套滑動原理,Android View 的事件分發原理解析_貨運

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

NestedScrollView 是用於替代 ScrollView 來解決嵌套滑動過程中的滑動事件的衝突。作為開發者,你會發現很多地方會用到嵌套滑動的邏輯,比如下拉刷新頁面,京東或者淘寶的各種商品頁面。

那為什麼要去了解 NestedScrollView 的源碼呢?那是因為 NestedScrollView 是嵌套滑動實現的模板範例,通過研讀它的源碼,能夠讓你知道如何實現嵌套滑動,然後如果需求上 NestedScrollView 無法滿足的時候,你可以自定義。

嵌套滑動

說到嵌套滑動,就得說說這兩個類了:NestedScrollingParent3 和 NestedScrollingChild3 ,當然同時也存在後面不帶数字的類。之所以後面帶数字了,是為了解決之前的版本遺留的問題:fling 的時候涉及嵌套滑動,無法透傳到另一個View 上繼續 fling,導致滑動效果大打折扣 。

其實 NestedScrollingParent2 相比 NestedScrollingParent 在方法調用上多了一個參數 type,用於標記這個滑動是如何產生的。type 的取值如下:

    /**
     * Indicates that the input type for the gesture is from a user touching the screen. 觸摸產生的滑動
     */
    public static final int TYPE_TOUCH = 0;

    /**
     * Indicates that the input type for the gesture is caused by something which is not a user
     * touching a screen. This is usually from a fling which is settling.  簡單理解就是fling
     */
    public static final int TYPE_NON_TOUCH = 1;

嵌套滑動,說得通俗點就是子 view 和 父 view 在滑動過程中,互相通信決定某個滑動是子view 處理合適,還是 父view 來處理。所以, Parent 和 Child 之間存在相互調用,遵循下面的調用關係:

上圖可以這麼理解:

  • ACTION_DOWN 的時候子 view 就要調用 startNestedScroll( ) 方法來告訴父 view 自己要開始滑動了(實質上是尋找能夠配合 child 進行嵌套滾動的 parent),parent 也會繼續向上尋找能夠配合自己滑動的 parent,可以理解為在做一些準備工作 。
  • 父 view 會收到 onStartNestedScroll 回調從而決定是不是要配合子 view 做出響應。如果需要配合,此方法會返回 true。繼而 onStartNestedScroll()回調會被調用。
  • 在滑動事件產生但是子 view 還沒處理前可以調用 dispatchNestedPreScroll(0,dy,consumed,offsetInWindow) 這個方法把事件傳給父 view,這樣父 view 就能在onNestedPreScroll 方法裏面收到子 view 的滑動信息,然後做出相應的處理把處理完后的結果通過 consumed 傳給子 view。

  • dispatchNestedPreScroll()之後,child可以進行自己的滾動操作。

  • 如果父 view 需要在子 view 滑動后處理相關事件的話可以在子 view 的事件處理完成之後調用 dispatchNestedScroll 然後父 view 會在 onNestedScroll 收到回調。

  • 最後,滑動結束,調用 onStopNestedScroll() 表示本次處理結束。

  • 但是,如果滑動速度比較大,會觸發 fling, fling 也分為 preFling 和 fling 兩個階段,處理過程和 scroll 基本差不多。 

NestedScrollView

首先是看類的名字

 class NestedScrollView extends FrameLayout implements NestedScrollingParent3,
 NestedScrollingChild3, ScrollingView {

可以發現它繼承了 FrameLayout,相當於它就是一個 ViewGroup,可以添加子 view , 但是需要注意的事,它只接受一個子 view,否則會報錯。

    @Override
    public void addView(View child) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child);
    }

    @Override
    public void addView(View child, int index) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, index);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, params);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, index, params);
    }

add view

對於 NestedScrollingParent3,NestedScrollingChild3 的作用,前文已經說了,如果還是不理解,後面再對源碼的分析過程中也會分析到。

其實這裏還可以提一下 RecyclerView:

public class RecyclerView extends ViewGroup implements ScrollingView,
        NestedScrollingChild2, NestedScrollingChild3 {

這裏沒有繼承 NestedScrollingParent3 是因為開發者覺得 RecyclerView 適合做一個子類。並且它的功能作為一個列表去展示,也就是不適合再 RecyclerView 內部去做一些複雜的嵌套滑動之類的。這樣 RecycylerView 外層就可以再嵌套一個 NestedScrollView 進行嵌套滑動了。後面再分析嵌套滑動的時候,也會把 RecycylerView 當作子類來進行分析,這樣能更好的理解源碼。

內部有個接口,使用者需要對滑動變化進行監聽的,可以添加這個回調:

    public interface OnScrollChangeListener {
        /**
         * Called when the scroll position of a view changes.
         *
         * @param v The view whose scroll position has changed.
         * @param scrollX Current horizontal scroll origin.
         * @param scrollY Current vertical scroll origin.
         * @param oldScrollX Previous horizontal scroll origin.
         * @param oldScrollY Previous vertical scroll origin.
         */
        void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
                int oldScrollX, int oldScrollY);
    }

構造函數

下面來看下構造函數:

    public NestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs,
            int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initScrollView();

        final TypedArray a = context.obtainStyledAttributes(
                attrs, SCROLLVIEW_STYLEABLE, defStyleAttr, 0);
        // 是否要鋪滿全屏
        setFillViewport(a.getBoolean(0, false));

        a.recycle();
        // 即是子類,又是父類
        mParentHelper = new NestedScrollingParentHelper(this);
        mChildHelper = new NestedScrollingChildHelper(this);

        // ...because why else would you be using this widget? 默認是滾動,不然你使用它就沒有意義了
        setNestedScrollingEnabled(true);

        ViewCompat.setAccessibilityDelegate(this, ACCESSIBILITY_DELEGATE);
    }    

這裏我們用了兩個輔助類來幫忙處理嵌套滾動時候的一些邏輯處理,NestedScrollingParentHelper,NestedScrollingChildHelper。這個是和前面的你實現的接口 NestedScrollingParent3,NestedScrollingChild3 相對應的。

下面看下  initScrollView 方法里的具體邏輯:

    private void initScrollView() {
        mScroller = new OverScroller(getContext());
        setFocusable(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
     // 會調用 ViewGroup 的 onDraw setWillNotDraw(
false); // 獲取 ViewConfiguration 中一些配置,包括滑動距離,最大最小速率等等 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); }

setFillViewport

在構造函數中,有這麼一個設定:

setFillViewport(a.getBoolean(0, false));

與 setFillViewport 對應的屬性是 android:fillViewport=”true”。如果不設置這個屬性為 true,可能會出現如下圖一樣的問題:

xml 布局:

<?xml version="1.0" encoding="utf-8"?>
<NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#fff000">
        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>
</NestedScrollView>

效果:

可以發現這個沒有鋪滿全屏,可是 xml 明明已經設置了 match_parent 了。這是什麼原因呢?

那為啥設置 true 就可以了呢?下面來看下它的 onMeasure 方法:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // false 直接返回
        if (!mFillViewport) {
            return;
        }

        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode == MeasureSpec.UNSPECIFIED) {
            return;
        }

        if (getChildCount() > 0) {
            View child = getChildAt(0);
            final NestedScrollView.LayoutParams lp = (LayoutParams) child.getLayoutParams();

            int childSize = child.getMeasuredHeight();
            int parentSpace = getMeasuredHeight()
                    - getPaddingTop()
                    - getPaddingBottom()
                    - lp.topMargin
                    - lp.bottomMargin;
            // 如果子 view 高度小於 父 view 高度,那麼需要重新設定高度
            if (childSize < parentSpace) {
                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin,
                        lp.width);
                // 這裏生成 MeasureSpec 傳入的是 parentSpace,並且用的是 MeasureSpec.EXACTLY 
                int childHeightMeasureSpec =
                        MeasureSpec.makeMeasureSpec(parentSpace, MeasureSpec.EXACTLY);
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

當你將 mFillViewport 設置為 true 后,就會把父 View 高度給予子 view 。可是這個解釋了設置 mFillViewport 可以解決不能鋪滿屏幕的問題,可是沒有解決為啥 match_parent 無效的問題。

在回到類的繼承關係上,NestedScrollView 繼承的是 FrameLayout,也就是說,FrameLayout 應該和 NestedScrollView 擁有一樣的問題。可是當你把 xml 中的布局換成 FrameLayout 后,你發現竟然沒有問題。那麼這是為啥呢?

原因是 NestedScrollView 又重寫了 measureChildWithMargins 。子view 的 childHeightMeasureSpec 中的 mode 是 MeasureSpec.UNSPECIFIED 。當被設置為這個以後,子 view 的高度就完全是由自身的高度決定了。

    @Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        // 在生成子 view 的 MeasureSpec 時候,傳入的是 MeasureSpec.UNSPECIFIED
        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

比如子 view 是 LinearLayout ,這時候,它的高度就是子 view 的高度之和。而且,這個 MeasureSpec.UNSPECIFIED 會一直影響着後面的子子孫孫 view 。

我猜這麼設計的目的是因為你既然使用了 NestedScrollView,就沒必要在把子 View  搞得跟屏幕一樣大了,它該多大就多大,不然你滑動的時候,看見一大片空白體驗也不好啊。

而 ViewGroup 中,measureChildWithMargins 的方法是這樣的:

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

由於一般使用 NestedScrollView 的時候,都是會超過屏幕高度的,所以不設置這個屬性為 true 也沒有關係。

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

繪製

既然前面已經把 onMeasure 講完了,那索引把繪製這塊都講了把。下面是 draw 方法,這裏主要是繪製邊界的陰影:

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (mEdgeGlowTop != null) {
            final int scrollY = getScrollY();
       // 上邊界陰影繪製
if (!mEdgeGlowTop.isFinished()) { final int restoreCount = canvas.save(); int width = getWidth(); int height = getHeight(); int xTranslation = 0; int yTranslation = Math.min(0, scrollY); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || getClipToPadding()) { width -= getPaddingLeft() + getPaddingRight(); xTranslation += getPaddingLeft(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getClipToPadding()) { height -= getPaddingTop() + getPaddingBottom(); yTranslation += getPaddingTop(); } canvas.translate(xTranslation, yTranslation); mEdgeGlowTop.setSize(width, height); if (mEdgeGlowTop.draw(canvas)) { ViewCompat.postInvalidateOnAnimation(this); } canvas.restoreToCount(restoreCount); }
       // 底部邊界陰影繪製
if (!mEdgeGlowBottom.isFinished()) { final int restoreCount = canvas.save(); int width = getWidth(); int height = getHeight(); int xTranslation = 0; int yTranslation = Math.max(getScrollRange(), scrollY) + height; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || getClipToPadding()) { width -= getPaddingLeft() + getPaddingRight(); xTranslation += getPaddingLeft(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getClipToPadding()) { height -= getPaddingTop() + getPaddingBottom(); yTranslation -= getPaddingBottom(); } canvas.translate(xTranslation - width, yTranslation); canvas.rotate(180, width, 0); mEdgeGlowBottom.setSize(width, height); if (mEdgeGlowBottom.draw(canvas)) { ViewCompat.postInvalidateOnAnimation(this); } canvas.restoreToCount(restoreCount); } } }

onDraw 是直接用了父類的,這個沒啥好講的,下面看看 onLayout:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        mIsLayoutDirty = false;
        // Give a child focus if it needs it
        if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
            scrollToChild(mChildToScrollTo);
        }
        mChildToScrollTo = null;

        if (!mIsLaidOut) { // 是否是第一次調用onLayout // If there is a saved state, scroll to the position saved in that state.
            if (mSavedState != null) {
                scrollTo(getScrollX(), mSavedState.scrollPosition);
                mSavedState = null;
            } // mScrollY default value is "0"

            // Make sure current scrollY position falls into the scroll range.  If it doesn't,
            // scroll such that it does.
            int childSize = 0;
            if (getChildCount() > 0) {
                View child = getChildAt(0);
                NestedScrollView.LayoutParams lp = (LayoutParams) child.getLayoutParams();
                childSize = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            }
            int parentSpace = b - t - getPaddingTop() - getPaddingBottom();
            int currentScrollY = getScrollY();
            int newScrollY = clamp(currentScrollY, parentSpace, childSize);
            if (newScrollY != currentScrollY) {
                scrollTo(getScrollX(), newScrollY);
            }
        }

        // Calling this with the present values causes it to re-claim them
        scrollTo(getScrollX(), getScrollY());
        mIsLaidOut = true;
    }

onLayout 方法也沒什麼說的,基本上是用了父類 FrameLayout 的布局方法,加入了一些 scrollTo 操作滑動到指定位置。

嵌套滑動分析

如果對滑動事件不是很清楚的小夥伴可以先看看這篇文章:Android View 的事件分發原理解析。

在分析之前,先做一個假設,比如 RecyclerView 就是 NestedScrollView 的子類,這樣去分析嵌套滑動更容易理解。這時候,用戶點擊 RecyclerView 觸發滑動。需要分析整個滑動過程的事件傳遞。

dispatchTouchEvent

這裏,NestedScrollView 用的是父類的處理,並沒有添加自己的邏輯。

onInterceptTouchEvent

當事件進行分發前,ViewGroup 首先會調用 onInterceptTouchEvent 詢問自己要不要進行攔截,不攔截,就會分發傳遞給子 view。一般來說,對於 ACTION_DOWN 都不會攔截,這樣子類有機會獲取事件,只有子類不處理,才會再次傳給父 View 來處理。下面來看看其具體代碼邏輯:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onMotionEvent will be called and we do the actual
         * scrolling there.
         */

        /*
        * Shortcut the most recurring case: the user is in the dragging
        * state and he is moving his finger.  We want to intercept this
        * motion.
        */
        final int action = ev.getAction();
     // 如果已經在拖動了,說明已經在滑動了,直接返回 true
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { /* * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check * whether the user has moved far enough from his original down touch. */ /* * Locally do absolute value. mLastMotionY is set to the y value * of the down event. */ final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { // If we don't have a valid id, the touch down wasn't on content. 不是一個有效的id break; } final int pointerIndex = ev.findPointerIndex(activePointerId); if (pointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + activePointerId + " in onInterceptTouchEvent"); break; } final int y = (int) ev.getY(pointerIndex);
          // 計算垂直方向上滑動的距離
final int yDiff = Math.abs(y - mLastMotionY);
          // 確定可以產生滾動了
if (yDiff > mTouchSlop && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) { mIsBeingDragged = true; mLastMotionY = y; initVelocityTrackerIfNotExists();
            // 可以獲取滑動速率 mVelocityTracker.addMovement(ev); mNestedYOffset
= 0; final ViewParent parent = getParent(); if (parent != null) {
               // 讓父 view 不要攔截,這裏應該是為了保險起見,因為既然已經走進來了,只要你返回 true,父 view 就不會攔截了。 parent.requestDisallowInterceptTouchEvent(
true); } } break; } case MotionEvent.ACTION_DOWN: { final int y = (int) ev.getY();
          // 如果點擊的範圍不在子 view 上,直接break,比如自己設置了很大的 margin,此時用戶點擊這裏,這個範圍理論上是不參与滑動的
if (!inChild((int) ev.getX(), y)) { mIsBeingDragged = false; recycleVelocityTracker(); break; } /* * Remember location of down touch. * ACTION_DOWN always refers to pointer index 0. */ mLastMotionY = y; mActivePointerId = ev.getPointerId(0);           // 在收到 DOWN 事件的時候,做一些初始化的工作 initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); /* * If being flinged and user touches the screen, initiate drag; * otherwise don't. mScroller.isFinished should be false when * being flinged. We need to call computeScrollOffset() first so that * isFinished() is correct. */ mScroller.computeScrollOffset();
          // 如果此時正在fling, isFinished 會返回 flase mIsBeingDragged
= !mScroller.isFinished();
          // 開始滑動 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: /* Release the drag */ mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); }
          // 手抬起后,停止滑動 stopNestedScroll(ViewCompat.TYPE_TOUCH);
break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } /* * The only time we want to intercept motion events is if we are in the * drag mode. */ return mIsBeingDragged; }

onInterceptTouchEvent 事件就是做一件事,決定事件是不是要繼續交給自己的 onTouchEvent 處理。這裏需要注意的一點是,如果子 view 在 dispatchTouchEvent 中調用了:

parent.requestDisallowInterceptTouchEvent(true)

那麼,其實就不會再調用 onInterceptTouchEvent 方法。也就是說上面的邏輯就不會走了。但是可以發現,down 事件,一般是不會攔截的。但是如果正在 fling,此時就會返回 true,直接把事件全部攔截。

那看下 RecyclerView 的 dispatchTouchEvent 是父類的,沒啥好分析的。而且它的 onInterceptTouchEvent 也是做了一些初始化的一些工作,和 NestedScrollView 一樣沒啥可說的。

onTouchEvent

再說 NestedScrollView 的 onTouchEvent。

對於 onTouchEvent 得分兩類進行討論,如果其子 view 不是 ViewGroup ,且是不可點擊的,就會把事件直接交給 NestedScrollView 來處理。

但是如果點擊的子 view 是 RecyclerView 的 ViewGroup 。當 down 事件來的時候,ViewGroup 的子 view 沒有處理,那麼就會交給 ViewGroup 來處理,你會發現ViewGroup 的 onTouchEvent 是默認返回 true 的。也就是說事件都是由  RecyclerView 來處理的。

這時候來看下 NestedScrollView 的 onTouchEvent 代碼:

 public boolean onTouchEvent(MotionEvent ev) {
        initVelocityTrackerIfNotExists();

        MotionEvent vtev = MotionEvent.obtain(ev);

        final int actionMasked = ev.getActionMasked();

        if (actionMasked == MotionEvent.ACTION_DOWN) {
            mNestedYOffset = 0;
        }
        vtev.offsetLocation(0, mNestedYOffset);

        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN: {
          // 需要有一個子類才可以進行滑動
if (getChildCount() == 0) { return false; }
          // 前面提到如果用戶在 fling 的時候,觸碰,此時是直接攔截返回 true,自己來處理事件。
if ((mIsBeingDragged = !mScroller.isFinished())) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } /* * If being flinged and user touches, stop the fling. isFinished * will be false if being flinged.處理結果就是停止 fling */ if (!mScroller.isFinished()) { mScroller.abortAnimation(); } // Remember where the motion event started mLastMotionY = (int) ev.getY(); mActivePointerId = ev.getPointerId(0);
         // 尋找嵌套父View,告訴它準備在垂直方向上進行 TOUCH 類型的滑動 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
break; } case MotionEvent.ACTION_MOVE: final int activePointerIndex = ev.findPointerIndex(mActivePointerId); if (activePointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); break; } final int y = (int) ev.getY(activePointerIndex); int deltaY = mLastMotionY - y;
          // 滑動前先把移動距離告訴嵌套父View,看看它要不要消耗,返回 true 代表消耗了部分距離
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset, ViewCompat.TYPE_TOUCH)) { deltaY -= mScrollConsumed[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; }
          // 滑動距離大於最大最小觸發距離
if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); }
            // 觸發滑動 mIsBeingDragged
= true; if (deltaY > 0) { deltaY -= mTouchSlop; } else { deltaY += mTouchSlop; } } if (mIsBeingDragged) { // Scroll to follow the motion event mLastMotionY = y - mScrollOffset[1]; final int oldY = getScrollY(); final int range = getScrollRange(); final int overscrollMode = getOverScrollMode(); boolean canOverscroll = overscrollMode == View.OVER_SCROLL_ALWAYS || (overscrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); // Calling overScrollByCompat will call onOverScrolled, which // calls onScrollChanged if applicable.
            // 該方法會觸發自身內容的滾動
if (overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0, 0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) { // Break our velocity if we hit a scroll barrier. mVelocityTracker.clear(); } final int scrolledDeltaY = getScrollY() - oldY; final int unconsumedY = deltaY - scrolledDeltaY;
            // 通知嵌套的父 View 我已經處理完滾動了,該你來處理了
if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset, ViewCompat.TYPE_TOUCH)) {
              // 如果嵌套父View 消耗了滑動,那麼需要更新 mLastMotionY
-= mScrollOffset[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } else if (canOverscroll) { ensureGlows(); final int pulledToY = oldY + deltaY;
               // 觸發邊緣的陰影效果
if (pulledToY < 0) { EdgeEffectCompat.onPull(mEdgeGlowTop, (float) deltaY / getHeight(), ev.getX(activePointerIndex) / getWidth()); if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } } else if (pulledToY > range) { EdgeEffectCompat.onPull(mEdgeGlowBottom, (float) deltaY / getHeight(), 1.f - ev.getX(activePointerIndex) / getWidth()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } } if (mEdgeGlowTop != null && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) { ViewCompat.postInvalidateOnAnimation(this); } } } break; case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
          // 計算滑動速率
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
          // 大於最小的設定的速率,觸發fling
if ((Math.abs(initialVelocity) > mMinimumVelocity)) { flingWithNestedDispatch(-initialVelocity); } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } mActivePointerId = INVALID_POINTER; endDrag(); break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged && getChildCount() > 0) { if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } } mActivePointerId = INVALID_POINTER; endDrag(); break; case MotionEvent.ACTION_POINTER_DOWN: { final int index = ev.getActionIndex(); mLastMotionY = (int) ev.getY(index); mActivePointerId = ev.getPointerId(index); break; } case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); break; } if (mVelocityTracker != null) { mVelocityTracker.addMovement(vtev); } vtev.recycle(); return true; }

ACTION_DOWN

先看 down 事件,如果處於 fling 期間,那麼直接停止 fling, 接着會調用 startNestedScroll,會讓 NestedScrollView 作為子 view 去 通知嵌套父 view,那麼就需要找到有沒有可以嵌套滑動的父 view 。

    public boolean startNestedScroll(int axes, int type) {
        // 交給 mChildHelper 代理來處理相關邏輯
        return mChildHelper.startNestedScroll(axes, type);
    }


    public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
        // 找到嵌套父 view 了,就直接返回
        if (hasNestedScrollingParent(type)) {
            // Already in progress
            return true;
        }
        // 是否支持嵌套滾動
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            while (p != null) {  // while 循環,將支持嵌套滑動的父 View 找出來。
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                    // 把父 view 設置進去
                    setNestedScrollingParentForType(type, p);
                    // 找到后,通過該方法可以做一些初始化操作
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }            

可以看到,這時候主要就是為了找到嵌套父 view。當 ViewParentCompat.onStartNestedScroll 返回 true,就表示已經找到嵌套滾動的父 View 了 。下面來看下這個方法的具體邏輯:

    // ViewParentCompat  
    public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
            int nestedScrollAxes, int type) {
        if (parent instanceof NestedScrollingParent2) {
            // First try the NestedScrollingParent2 API
            return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
                    nestedScrollAxes, type);
        } else if (type == ViewCompat.TYPE_TOUCH) {
            // Else if the type is the default (touch), try the NestedScrollingParent API
            if (Build.VERSION.SDK_INT >= 21) {
                try {
                    return parent.onStartNestedScroll(child, target, nestedScrollAxes);
                } catch (AbstractMethodError e) {
                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
                            + "method onStartNestedScroll", e);
                }
            } else if (parent instanceof NestedScrollingParent) {
                return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
                        nestedScrollAxes);
            }
        }
        return false;
    }

這裏其實沒啥好分析,就是告訴父類當前是什麼類型的滾動,以及滾動方向。其實這裏可以直接看下 NestedScrollView 的 onStartNestedScroll 的邏輯。

//  NestedScrollView
    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes,
            int type) {
     // 確保觸發的是垂直方向的滾動
return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; }

當確定了嵌套父 View 以後,又會調用父 view 的  onNestedScrollAccepted 方法,在這裏可以做一些準備工作和配置。下面我們看到的 是 Ns 裏面的方法,注意不是父 view 的,只是當作參考。

public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {
    mParentHelper.onNestedScrollAccepted(child, target, axes, type);
   // 這裏 Ns 作為子 view 調用 該方法去尋找嵌套父 view。注意這個方法會被調用是 NS 作為父 view 收到的。這樣就 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, type); }

到這裏,down 的作用就講完了。

ACTION_MOVE 

首先是會調用 dispatchNestedPreScroll,講當前的滑動距離告訴嵌套父 View。

  public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
            int type) {
     // Ns 作為子 view 去通知父View
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type); } 

下面看下 mChildHelper 的代碼邏輯:

    /**
     * Dispatch one step of a nested pre-scrolling operation to the current nested scrolling parent.
     *
     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
     * method/{@link androidx.core.view.NestedScrollingChild2} interface method with the same
     * signature to implement the standard policy.</p>
     *
     * @return true if the parent consumed any of the nested scroll
     */
    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
            @Nullable int[] offsetInWindow, @NestedScrollType int type) {
        if (isNestedScrollingEnabled()) {
       // 獲取之前找到的嵌套滾動的父 View
final ViewParent parent = getNestedScrollingParentForType(type); if (parent == null) { return false; }        // 滑動距離肯定不為0 才有意義 if (dx != 0 || dy != 0) { int startX = 0; int startY = 0; if (offsetInWindow != null) { mView.getLocationInWindow(offsetInWindow); startX = offsetInWindow[0]; startY = offsetInWindow[1]; } if (consumed == null) { if (mTempNestedScrollConsumed == null) { mTempNestedScrollConsumed = new int[2]; } consumed = mTempNestedScrollConsumed; } consumed[0] = 0; consumed[1] = 0;
          // 調用嵌套父 View 的對應的回調 ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);
if (offsetInWindow != null) { mView.getLocationInWindow(offsetInWindow); offsetInWindow[0] -= startX; offsetInWindow[1] -= startY; } return consumed[0] != 0 || consumed[1] != 0; } else if (offsetInWindow != null) { offsetInWindow[0] = 0; offsetInWindow[1] = 0; } } return false; }

這裏主要是將滑動距離告訴 父 view,有消耗就會返回 true 。

    // ViewParentCompat
    public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
            int[] consumed) {
        onNestedPreScroll(parent, target, dx, dy, consumed, ViewCompat.TYPE_TOUCH);
    }

其實下面的 onNestedPreScroll 跟前面的 onStartNestedScroll 邏輯很像,就是層層傳遞。

    public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
            int[] consumed, int type) {
        if (parent instanceof NestedScrollingParent2) {
            // First try the NestedScrollingParent2 API
            ((NestedScrollingParent2) parent).onNestedPreScroll(target, dx, dy, consumed, type);
        } else if (type == ViewCompat.TYPE_TOUCH) {
            // Else if the type is the default (touch), try the NestedScrollingParent API
            if (Build.VERSION.SDK_INT >= 21) {
                try {
                    parent.onNestedPreScroll(target, dx, dy, consumed);
                } catch (AbstractMethodError e) {
                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
                            + "method onNestedPreScroll", e);
                }
            } else if (parent instanceof NestedScrollingParent) {
                ((NestedScrollingParent) parent).onNestedPreScroll(target, dx, dy, consumed);
            }
        }
    }

下面為了方便,沒法查看 NS 的嵌套父 View 的邏輯。直接看 Ns 中對應的方法。

    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
            int type) {
     // 最終也是 Ns 再傳給其嵌套父 View dispatchNestedPreScroll(dx, dy, consumed,
null, type); }

傳遞完了之後,就會調用  overScrollByCompat 來實現滾動。

    boolean overScrollByCompat(int deltaX, int deltaY,
            int scrollX, int scrollY,
            int scrollRangeX, int scrollRangeY,
            int maxOverScrollX, int maxOverScrollY,
            boolean isTouchEvent) {
        final int overScrollMode = getOverScrollMode();
        final boolean canScrollHorizontal =
                computeHorizontalScrollRange() > computeHorizontalScrollExtent();
        final boolean canScrollVertical =
                computeVerticalScrollRange() > computeVerticalScrollExtent();
        final boolean overScrollHorizontal = overScrollMode == View.OVER_SCROLL_ALWAYS
                || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
        final boolean overScrollVertical = overScrollMode == View.OVER_SCROLL_ALWAYS
                || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);

        int newScrollX = scrollX + deltaX;
        if (!overScrollHorizontal) {
            maxOverScrollX = 0;
        }

        int newScrollY = scrollY + deltaY;
        if (!overScrollVertical) {
            maxOverScrollY = 0;
        }

        // Clamp values if at the limits and record
        final int left = -maxOverScrollX;
        final int right = maxOverScrollX + scrollRangeX;
        final int top = -maxOverScrollY;
        final int bottom = maxOverScrollY + scrollRangeY;

        boolean clampedX = false;
        if (newScrollX > right) {
            newScrollX = right;
            clampedX = true;
        } else if (newScrollX < left) {
            newScrollX = left;
            clampedX = true;
        }

        boolean clampedY = false;
        if (newScrollY > bottom) {
            newScrollY = bottom;
            clampedY = true;
        } else if (newScrollY < top) {
            newScrollY = top;
            clampedY = true;
        }

        if (clampedY && !hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) {
            mScroller.springBack(newScrollX, newScrollY, 0, 0, 0, getScrollRange());
        }
     
        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);

        return clampedX || clampedY;
    }

整塊邏輯其實沒啥好說的,然後主要是看 onOverScrolled 這個方法:

   protected void onOverScrolled(int scrollX, int scrollY,
            boolean clampedX, boolean clampedY) {
        super.scrollTo(scrollX, scrollY);
    }

最終是調用 scrollTo 方法來實現了滾動。

當滾動完了后,會調用 dispatchNestedScroll 告訴父 view 當前還剩多少沒消耗,如果是 0,那麼就不會上傳,如果沒消耗完,就會傳給父 View 。

如果是子 View 傳給 NS 的,是會通過 scrollBy 來進行消耗的,然後繼續向上層傳遞。

    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
            int dyUnconsumed, int type) {
        final int oldScrollY = getScrollY();
        scrollBy(0, dyUnconsumed);
        final int myConsumed = getScrollY() - oldScrollY;
        final int myUnconsumed = dyUnconsumed - myConsumed;
        dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null,
                type);
    }

假設當前已經滑動到頂部了,此時繼續滑動的話,就會觸發邊緣的陰影效果。

ACTION_UP

當用戶手指離開后,如果滑動速率超過最小的滑動速率,就會調用 flingWithNestedDispatch(-initialVelocity) ,下面來看看這個方法的具體邏輯:

    private void flingWithNestedDispatch(int velocityY) {
        final int scrollY = getScrollY();
        final boolean canFling = (scrollY > 0 || velocityY > 0)
                && (scrollY < getScrollRange() || velocityY < 0);
     // fling 前問問父View 要不要 fling, 一般是返回 false
if (!dispatchNestedPreFling(0, velocityY)) {
       // 這裏主要是告訴父類打算自己消耗了 dispatchNestedFling(
0, velocityY, canFling);
       // 自己處理 fling(velocityY); } }

下面繼續看 fling 的實現。

    public void fling(int velocityY) {
        if (getChildCount() > 0) {

            mScroller.fling(getScrollX(), getScrollY(), // start
                    0, velocityY, // velocities
                    0, 0, // x
                    Integer.MIN_VALUE, Integer.MAX_VALUE, // y
                    0, 0); // overscroll
            runAnimatedScroll(true);
        }
    }

    private void runAnimatedScroll(boolean participateInNestedScrolling) {
        if (participateInNestedScrolling) {
            // fling 其實也是一種滾動,只不過是非接觸的
            startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
        } else {
            stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
        }
        mLastScrollerY = getScrollY();
        ViewCompat.postInvalidateOnAnimation(this);
    }

最終會觸發重繪操作,重繪過程中會調用 computeScroll,下面看下其內部的代碼邏輯。

    @Override
    public void computeScroll() {

        if (mScroller.isFinished()) {
            return;
        }

        mScroller.computeScrollOffset();
        final int y = mScroller.getCurrY();
        int unconsumed = y - mLastScrollerY;
        mLastScrollerY = y;

        // Nested Scrolling Pre Pass
        mScrollConsumed[1] = 0;
     // 滾動的時候,依然會把當前的未消耗的滾動距離傳給嵌套父View dispatchNestedPreScroll(
0, unconsumed, mScrollConsumed, null, ViewCompat.TYPE_NON_TOUCH); unconsumed -= mScrollConsumed[1]; final int range = getScrollRange(); if (unconsumed != 0) { // Internal Scroll final int oldScrollY = getScrollY();
       // 自己消耗 overScrollByCompat(
0, unconsumed, getScrollX(), oldScrollY, 0, range, 0, 0, false); final int scrolledByMe = getScrollY() - oldScrollY; unconsumed -= scrolledByMe; // Nested Scrolling Post Pass mScrollConsumed[1] = 0;
        // 繼續上傳給父View dispatchNestedScroll(
0, scrolledByMe, 0, unconsumed, mScrollOffset, ViewCompat.TYPE_NON_TOUCH, mScrollConsumed); unconsumed -= mScrollConsumed[1]; }      // 如果到這裡有未消耗的,說明已經滾動到邊緣了 if (unconsumed != 0) { final int mode = getOverScrollMode(); final boolean canOverscroll = mode == OVER_SCROLL_ALWAYS || (mode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); if (canOverscroll) { ensureGlows(); if (unconsumed < 0) { if (mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); } } else { if (mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); } } }
       // 停止滾動   abortAnimatedScroll(); }      // 如果此時滾動還未結束,並且當前的滑動距離都被消耗了,那麼繼續刷新滾動,直到停止為止
if (!mScroller.isFinished()) { ViewCompat.postInvalidateOnAnimation(this); } }

到這裏,關於 Ns 的嵌套滑動就講完了。希望大家能夠對嵌套滑動有個理解。

閱讀 Ns 的源碼,可以讓你更好的理解嵌套滑動,以及事件分發的邏輯。

  本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

【Python】組合數據類型_網頁設計公司

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

集合類型

集合類型定義

集合是多個元素的無序組合

  • 集合類型與數學中的集合概念一致
  • 集合元素之間無序,每個元素唯一,不存在相同元素
  • 集合元素不可更改,不能是可變數據類型

    理解:因為集合類型不重複,所以不能更改,否則有可能重複。

集合是多個元素的無序組合

  • 集合用大括號 {} 表示,元素間用逗號分隔
  • 建立集合類型用 {}set()
  • 建立空集合類型,必須使用set()

集合操作符

操作符及應用 描述
S | T 並,返回一個新集合,包括在集合S和T中的所有元素
S – T 差,返回一個新集合,包括在集合S但不在T中的元素
S & T 交,返回一個新集合,包括同時在集合S和T中的元素
S ^ T 補,返回一個新集合,包括集合S和T中的非相同元素
S <= T 返回True/False,判斷S和T的子集關係
S < T 返回True/False,判斷S和T的子集關係
S >= T 返回True/False,判斷S和T的包含關係
S > T 返回True/False,判斷S和T的包含關係
S |= T 並,更新集合S,包括在集合S和T中的所有元素
S -= T 差,更新集合S,包括在集合S但不在T中的元素
S &= T 交,更新集合S,包括同時在集合S和T中的元素
S ^= T 補,更新集合S,包括集合S和T中的非相同元素

集合處理方法

操作函數或方法 描述
S.add(x) 如果x不在集合S中,將x增加到S
S.discard(x) 移除S中元素x,如果x不在集合S中,不報錯
S.remove(x) 移除S中元素x,如果x不在集合S中,產生KeyError異常
S.clear() 移除S中所有元素
S.pop() 隨機返回S的一個元素,更新S,若S為空產生KeyError異常
S.copy() 返回集合S的一個副本
len(S) 返回集合S的元素個數
x in S 判斷S中元素x,x在集合S中,返回True,否則返回False
x not in S 判斷S中元素x,x不在集合S中,返回True,否則返回False
set(x) 將其他類型變量x轉變為集合類型

集合類型應用場景

數據去重:集合類型所有元素無重複

序列類型

序列類型定義

序列是具有先後關係的一組元素

  • 序列是一維元素向量,元素類型可以不同
  • 類似數學元素序列: s0, s1, … , sn-1
  • 元素間由序號引導,通過下標訪問序列的特定元素

序列處理函數及方法

操作符及應用 描述
x in s 如果x是序列s的元素,返回True,否則返回False
x not in s 如果x是序列s的元素,返回False,否則返回True
s + t 連接兩個序列s和t
s*n 或 n*s 將序列s複製n次
s[i] 索引,返回s中的第i個元素,i是序列的序號
s[i: j]
s[i: j: k]
切片,返回序列s中第i到j以k為步長的元素子序列
函數和方法 描述
len(s) 返回序列s的長度,即元素個數
min(s) 返回序列s的最小元素,s中元素需要可比較
max(s) 返回序列s的最大元素,s中元素需要可比較
s.index(x)
s.index(x, i, j)
返回序列s從i開始到j位置中第一次出現元素x的位置
s.count(x) 返回序列s中出現x的總次數

元組類型及操作

元組是序列類型的一種擴展

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

  • 元組是一種序列類型,一旦創建就不能被修改
  • 使用小括號 ()tuple() 創建,元素間用逗號 , 分隔
  • 可以使用或不使用小括號

元組繼承序列類型的全部通用操作

  • 元組繼承了序列類型的全部通用操作
  • 元組因為創建后不能修改,因此沒有特殊操作
  • 使用或不使用小括號

列表類型及操作

列表是序列類型的一種擴展,十分常用

  • 列表是一種序列類型,創建后可以隨意被修改
  • 使用方括號 [] 或list() 創建,元素間用逗號 , 分隔
  • 列表中各元素類型可以不同,無長度限制
函數或方法 描述
ls[i] = x 替換列表ls第i元素為x
ls[i: j: k] = lt 用列表lt替換ls切片后所對應元素子列表
del ls[i] 刪除列表ls中第i元素
del ls[i: j: k] 刪除列表ls中第i到第j以k為步長的元素
ls += lt 更新列表ls,將列表lt元素增加到列表ls中
ls *= n 更新列表ls,其元素重複n次
函數或方法 描述
ls.append(x) 在列表ls最後增加一個元素x
ls.clear() 刪除列表ls中所有元素
ls.copy() 生成一個新列表,賦值ls中所有元素
ls.insert(i,x) 在列表ls的第i位置增加元素x
ls.pop(i) 將列表ls中第i位置元素取出並刪除該元素
ls.remove(x) 將列表ls中出現的第一個元素x刪除
ls.reverse() 將列表ls中的元素反轉

序列類型應用場景

數據表示:元組 和 列表

  • 元組用於元素不改變的應用場景,更多用於固定搭配場景
  • 列表更加靈活,它是最常用的序列類型
  • 最主要作用:表示一組有序數據,進而操作它們

元素遍歷

數據保護

  • 如果不希望數據被程序所改變,轉換成元組類型

字典

字典類型定義

  • 映射是一種鍵(索引)和值(數據)的對應
  • 鍵值對:鍵是數據索引的擴展
  • 字典是鍵值對的集合,鍵值對之間無序
  • 採用大括號{}dict()創建,鍵值對用冒號: 表示

{<鍵1>:<值1>, <鍵2>:<值2>, … , <鍵n>:<值n>}

<字典變量> = {<鍵1>:<值1>, … , <鍵n>:<值n>}
<值> = <字典變量>[<鍵>]
<字典變量>[<鍵>] = <值>
[ ] 用來向字典變量中索引或增加元素

字典處理函數及方法

函數或方法 描述
del d[k] 刪除字典d中鍵k對應的數據值
k in d 判斷鍵k是否在字典d中,如果在返回True,否則False
d.keys() 返回字典d中所有的鍵信息
d.values() 返回字典d中所有的值信息
d.items() 返回字典d中所有的鍵值對信息
d.get(k, <default>) 鍵k存在,則返回相應值,不在則返回 值
d.pop(k, <default>) 鍵k存在,則取出相應值,不在則返回 值
d.popitem() 隨機從字典d中取出一個鍵值對,以元組形式返回
d.clear() 刪除所有的鍵值對
len(d) 返回字典d中元素的個數

字典類型應用場景

映射的表達

  • 映射無處不在,鍵值對無處不在
  • 例如:統計數據出現的次數,數據是鍵,次數是值
  • 最主要作用:表達鍵值對數據,進而操作它們

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

這台飛度飛起來了!超強颱風殺到,要注意什麼_網頁設計公司電動車,網頁設計公司,網頁設計

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

太恐怖了趕緊躲進房間里吧可是我今天約了老漢吃飯答謝他上次推車的恩情呢那麼也就是說必須開車出去咯。好的,那就讓小編美美支招颱風天用車要注意什麼。首先如果你的車是飛度的話為了不讓它飛起來請拉上你的親朋好友坐車增加車重開玩笑颱風天汽車受損主要有2種情況第一,水淹颱風往往伴隨着強降雨目前的城市排水能力有限所以在很多地勢低洼的地方容易積水颱風天一旦把車子停在那些地方分分鐘變成落湯雞鐺鐺鐺鐺,一台泡水車誕生了第二,落物砸壞颱風一來各種亂七八糟的東西吹得滿天都是尤其是人家窗檯放的花花草草啊分分鐘砸下來車頂變形玻璃破碎第

據線人報道

颱風“薩瓦迪卡”就要抵達海南

sorry是颱風“莎莉嘉”

莎莉嘉是什麼來頭呢?

我們可以看看這個魔鬼的生平

10月13日莎莉嘉颱風生成

經過2天的卧薪嘗膽

10月15日莎莉嘉成長為颱風級

一天之內又晉陞為強颱風級

10月16日

這個魔鬼就殺入菲律賓呂宋半島

10月17日

莎莉嘉來到海南省萬寧市東偏南方大約570公里的南海中部

最強風力13級(38米/秒)

12級的大風就可以把列車吹翻

可以把20噸重的汽油罐拋到80米的高空

按照這個速度

10月19日將正式殺入廣東

沒錯,

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

也就是今天!

太恐怖了

趕緊躲進房間里吧

可是我今天約了老漢吃飯

答謝他上次推車的恩情呢

那麼也就是說必須開車出去咯?

好的,那就讓小編美美支招

颱風天用車

要注意什麼?

首先如果你的車是飛度的話

為了不讓它飛起來

請拉上你的親朋好友坐車

增加車重

開玩笑

颱風天汽車受損

主要有2種情況

第一,水淹

颱風往往伴隨着強降雨

目前的城市排水能力有限

所以在很多地勢低洼的地方容易積水

颱風天一旦把車子停在那些地方

分分鐘變成落湯雞

鐺鐺鐺鐺,一台泡水車誕生了

第二,落物砸壞

颱風一來

各種亂七八糟的東西吹得滿天都是

尤其是人家窗檯放的花花草草啊

分分鐘砸下來

車頂變形

玻璃破碎

第三,車被吹走了

如果一大早醒來

車子被吹走了

請立即抱緊你的男/女朋友

此時風力肯定非常大

當然有的人就能巋然不動

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

【教授挑車】配置高檔次高!這四款超人氣SUV讓我很糾結!_台北網頁設計

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

就覺得博越能把做工和舒適性做好就基本對得起消費者了,畢竟真正追求動力操控的人不會買SUV。長安CS75指導價:9。28-15。88萬似乎銷量比較好的那幾款自主品牌SUV在外觀都會有一些共通點,方正硬朗的線條,重心偏高的車身,讓整車看起來比較霸氣,CS75的外觀就是這麼一個風格。

話說車市正處於金9銀10的旺季,不少新晉車主都選擇在這兩個月購車,可是車子的配置並不是千篇一律的,不同車型之間,配置的差別是蠻大的。但認為有些配置最好是原裝就搭載的,如ESp、倒車雷達和倒車影像、胎壓監測、GpS導航等等。這幾輛SUV,就是認為配置達標,又值得購買的SUV,那不同車型之間要怎麼選?和你一起看看。

H6的外形好像千年不變,其實在細節上它也是與時俱進的。拿最新的藍標運動版來說,六邊形的鍍鉻格柵、頂配的氙氣燈、LED日間行車燈等元素,都讓H6顯得更加“潮”,而車身線條則依舊偏向圓潤,但同時又不失力量感。也看不出H6的外觀有哪些特別驚艷的地方,但它經典硬朗的外觀依然吸引不少的消費者,也許這就是它的魅力所在。

內飾方面也頗具運動元素,黑色的內飾搭配紅色的縫線,經典的運動組合,中控台的線條也十分流暢有層次感,覺得這種設計會比較耐看。

配置方面,看了一下,GpS導航的話,除了最低配的車型,其他都有配備,而ESp則全系標配,正常表現。超值型和都市型這兩個車型的倒車攝像和胎壓監測都沒有配備,除此以外,認為H6其他車型的配置都能滿足下地就能跑的配置需求。

至於H6的動力,它那台老掉牙的發動機就暫且不說了,1.5T排量車型的熱度是最高的,150pS的馬力對於一台SUV來說真的不咋地,2200轉左右渦輪介入后,加速會更加积極。認為H6的動力表現其實與它力量感十足的外觀不太相符,但哈弗的品質和做工在自主品牌來說,還是值得肯定的。

要聊博越的外觀好或不好,是肯定的,並不是因為它有多麼好看多美有魅力,而是覺得博越的外觀設計是真的很有原創性,很有心思的。譬如前臉上格柵的家族式造型,下格柵的如意和祥雲元素,雖然好壞見仁見智,但也體現出博越的外觀設計方面是下了功夫去做的。

博越的內飾其實帶有少許歐式的風格,飛機座艙式的布局,能更好的激發的駕駛慾望。中控台線條平直簡潔,十分匹配這種肌肉男。

配置方面,智悅型和智享型兩個低配車型都沒有胎壓監測、倒車影像,而GpS導航則需要高配車型才配備,但支持選裝。至於車身穩定、牽引力控制和剎車輔助這些主動安全配置都全系標配,可能是學習到沃爾沃的優點了。認為,想買博越的話還是建議中配的智尚型或者智慧型,性價比會較高。

動力方面,博越的表現也並不驚艷,雖然輸出比較線性,但動力的調校實在過於保守,1.8T的車型能提供最大163pS或184pS的馬力,實際駕駛想要完成超車等動作,油門還是踩深點,但該車的優點是底盤完整度夠高,

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

質感紮實。就覺得博越能把做工和舒適性做好就基本對得起消費者了,畢竟真正追求動力操控的人不會買SUV。

似乎銷量比較好的那幾款自主品牌SUV在外觀都會有一些共通點,方正硬朗的線條,重心偏高的車身,讓整車看起來比較霸氣,CS75的外觀就是這麼一個風格。除此以外,最吸引的要數它的大燈,帶透鏡的近光燈以及LED光源的示寬燈和日行燈,點亮以後效果還是十分炫酷的。

內飾方面,採用飛翼式的設計,材料主要為硬質塑料,從按鍵、內飾和中控屏的界面來看,還是感覺到明顯的廉價感,希望後續車型會有所提高。

配置方面,CS75的優勢就不太明顯,像GpS導航、倒車影像、胎壓監測、車身穩定控制這些配置,統一都是只有中高配的車型才具備,讓感到CS75 對於低配車型的關照實在是不夠啊,起碼ESp是需要全系標配才對得起十萬元級別的價格吧。

CS75的主力為搭載1.8T的車型,最大馬力177pS,採用6AT手自一體變速箱,實際表現的動力還是比較充足,超車的動作也不會十分拖沓。更讓滿意的是該車渦輪介入的動作不會很明顯,整體的動力輸出十分平順。配合紮實緊緻的底盤,舒適性很好,處理顛簸的動作很迅速,除了隔音表現一般般以外,CS75行駛品質還是能夠讓滿意的。

認為GS4的外觀設計是成功的,延續了傳祺家族的設計風格,“光影雕塑2.0”的設計理念把GS4的每一個細節都塑造得獨具個性。側面的線條豐富但不紊亂,配合懸浮式車頂的設計,十分時尚。

內飾方面,整體為鷗翼式的造型,線條比較圓滑流暢,細節處則使用了很多的六邊形和菱形的元素,讓GS4的內飾個性十足。

GS4的配置還是比較豐富,但發現該車的GpS導航、胎壓監測和倒車影像在低配甚至中配的部分車型都會缺乏,而它的博世9.1ESp除了舒適型以外,其他車型都有配備,還算可以接受,倒車雷達則是全系標配,這點倒讓滿意。

動力方面,有1.3T和1.5T兩種排量的車型,最大馬力分別是137pS和152pS,自動變速箱都是7速雙離合。也試駕過GS4,動力方面是可以的,提速起步都還算輕快,只是雙離合的換擋的時候還是會有頓挫感。至於底盤,質感是比較紮實的,濾震的效果也很到位,唯一要吐槽的,就是轉向的手感實在太虛,說白了就是轉向的準確度不夠高,但整體來說,GS4是很不錯的國產車了。

總結:聽整篇文章都在說這幾個配置,其實是有原因的,像GpS導航,在手機信號不好的偏遠地區,會起到十分重要的作用,而倒車雷達和攝像,對於一台體型高大的SUV來說是十分方便的一個配置,至於胎壓監測和ESp,這兩個就算是比較基本的安全配置了。所以大家購車的時候還是要看清楚配置,畢竟這些配置後期加裝都比較麻煩。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

終於帶T了!這款SUV換上1.5T能否干贏H6和博越?_網頁設計公司

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

接着換2擋,即使轉速回落至1000轉的上下,但隨着充足扭矩的緊接而來,車輛的提速依然爽快利落。站在大部分手動擋駕駛者的角度,在擁堵、頻發紅燈起步的情況下,發動機的低扭不足是最為影響體驗的一點(沒有之一)。在經過伊寧縣道的一個又一個路口以後,基本可以確認這台1。

“如果CS75能夠搭載小排量渦輪增壓發動機,那我一定會把它列入我的購車名單當中。”在某次乘搭滴滴專車的過程中,一位長安CS75的車主如是告訴我。

更符合消費需求的1.5T

這位車主的心聲道出了長安CS75在產品布局上存在的不足—消費者需要排量更小、售價更低的渦輪增壓車型以面對日常家用代步的用車需求,而這正是CS75所缺乏的。確實,CS75的2.0L車型由於排量的原因,無法享受到國家的小排量優惠政策。而1.8T車型相對同級競爭車型,12.28萬的起售價又缺乏一定的吸引力。在目前10-15萬級別自主品牌SUV的消費者最看重的兩點核心—價格、油耗上,CS75的產品定位並沒有拿捏到一個較好的平衡。

客觀反映在銷量上,長安CS75在近年的4月、6月、7月的同比銷量均出現了一定程度的下挫。對於長安來說,亟需一個解決方案,以幫助CS75重新站回自主品牌SUV第一梯隊的位置。於是,售價10.58-12.38萬元的CS75 1.5T車型(手動擋)正式被推出了市場。

賽里木湖,既是試車也是賞景

當然,能夠幫助CS75重回第一梯隊,相比起更全面的產品布局,產品的質量或許顯得更為重要。“CS75新車型的動力總成表現如何?”是包括我在內的眾多汽車媒體所關注的重點。於是,在緊接着新車型上市的當天,長安便在新疆的伊寧市為CS75舉辦了媒體試駕活動。因蘋果而出名的伊寧,在10月較其他城市更早地進入冬季。試駕當天的氣度只有6℃,並且從酒店向賽里木湖進發的試駕路程,是逐漸往2000+海拔爬升的過程。不過就試驗一款1.5T發動機的動力性能而言,是一個很理想的環境。

(風景實在太美,容我亂入顯擺兩張)

一台全新設計的發動機

在小排量的政策引導下,目前大多數的緊湊型SUV的渦輪排量都設計為1.4T、1.5T。譬如1.4T的大眾途觀、1.5T的傳祺GS4、1.5T的哈弗H6。而受技術、成本的限制,不少的自主品牌會直接採用三菱的代號4A91T的1.5T發動機,雖然在穩定性上佔據優勢,但使得像缸內直噴等更先進的發動機技術無法在發動機上運用。也因此,CS75並沒有直接採購三菱的發動機方案,而是基於長安的H平台進行研發。當然,後來的試駕證明,這是一個正確的選擇。隨行的長安工程師告訴我:“早在4年前,長安便開始了這款1.5T發動機的研發工作,主要的研發工作由長安位於英國伯明翰的發動機研發中心所完成。整個發動機結構均為全新設計,與1.8T以及2.0L發動機都不盡相同”

這台發動機運用了不少如今主流的發動機技術,相對於不少的自主品牌又是先進的。譬如缸內直噴技術、可變排量機油泵、自動啟停等,在這些技術的結合下,這台1.5T發動機擁有了125kW的最大功率以及230N.m/1950-4500rpm的最大扭矩。動力數據並非1.5T發動機里最強的,但與合資品牌對標也未嘗不可。

這能勝任全面的城市路況

在中高海拔的情況下,預想的低扭不足的情況並沒有出現,1擋起步發動機轉速迅速拉升至1500轉的上下,CS75已經具備了一定明顯的提速感,在紅燈路口的多次試驗我更加肯定這一點。當然實際的提速速度比我感受到的更快,原因在後面再說。接着換2擋,即使轉速回落至1000轉的上下,但隨着充足扭矩的緊接而來,

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

車輛的提速依然爽快利落。站在大部分手動擋駕駛者的角度,在擁堵、頻發紅燈起步的情況下,發動機的低扭不足是最為影響體驗的一點(沒有之一)。在經過伊寧縣道的一個又一個路口以後,基本可以確認這台1.5T的小排量發動機能夠很好地勝任城市擁堵的使用環境。我與長安的工程師分享這一點,其表示,這台1.5T發動機的渦輪增壓部件,由博格華納所提供,由於其可靠性以及對小排量渦輪增壓發動機的適配度較高,像大眾等一線的合資車企亦同樣從其採購。

(上海某知名媒體人士)

由於近年來國家對西部的政策扶持,使得伊寧的高速公路網得到了充分的發展,連接伊寧市區與賽里木湖的連霍高速就是其中的一條暫新的高速公路。這條將近200公里距離的路程,過去或許需要開上半天甚至更久,而當天我們頂着120公里的限速前行,花費的時間也只有一個半小時。

(頗為壯觀的果子溝大橋)

頂着高速限速前行,很自然地把擋位掛上6擋。考慮到CS75的SUV定位,6擋變速箱的變速齒比並沒有被設計得過小。120公里/小時巡航時,轉速指針的位置大致在2700rpm的範圍。要是加深油門的開度,發動機也樂於把轉速往3000rpm以上突破以換取更好的加速能力,但是受制於小排量的天生性結構,,車速突破100公里/小時以後,腳下油門的积極程度便已經很難與提速感相協調。

讓人歡喜的靜音以及振動抑制

不過發動機這種長時間處於高轉速運轉轉態的情況,也讓我發現這台1.5T發動機在靜音性以及震動抑制方面有着讓人歡喜的表現。針對小排量發動機特有的高速運轉震動、噪音的短板,工程師為這台1.5T發動機配置了低噪靜音皮帶、低噪噴油器等相對應的靜音、降噪措施。震動抑制方面的效果是出色的,和2.0L發動機的感官感受相差不多,來自上海某知名媒體老濕和我持同樣的意見。

因為私人座駕比較爛的緣故,我一直對汽車的靜音性比較在意。而CS75的靜音表現則可以形容為同級1.5T車型的前列。試駕過程中,偷懶加減檔操作的我,經常把轉速拉升至3000rpm的水平,但此時的發動機噪音確實要比我座駕的發動機處於2000rpm時的表現還要出色。這也解釋了前面CS75在中低速時的加速能力要比人體感官感受要來得更強,很大程度上這台1.5T的暴躁被掩蓋了起來。如果這一點要對標,不少主流合資品牌的緊湊型SUV能夠成為目標對象。

槽點:讓新手犯難的起步熄火

從動力、靜音、震動這三個方面上,這台1.5T發動機的表現都打消了我預期的擔心。得以輕鬆、愜意地在賽里木湖遊玩,很大部分要歸結於CS75的功勞。但儘管如此,手動版的CS75仍然有一個表較明顯的槽點—起步容易熄火。整個的試駕過程中,我在起步階段熄火了2次。其餘試駕的媒體或多或少也存在這樣的情況。對於老司機而言,可以通過快速的熟悉去解決這一問題。而對於剛拿駕照的新手司機而言,則需要更為合理的自動補油程序。

一份目前自主品牌所缺少的實在

其餘升級,像30公里可開啟的360全景影像、自動駐車等新增配置不加以贅敘。但有一點我認為必要着重提及,那便是位於車內后視鏡上方的行車記錄儀USB預留接口。“許多消費者加裝行車記錄儀都必須拆開中控台,從保險盒取電,這個新增的USB借口能夠避免了這些麻煩。”長安的工程師表示,在原有車型的基礎上,新款車型在細節上做了更多合理性的改進。儘管問題細小,但當中卻能夠透露出一份目前自主品牌所缺少的實在。

比以往的CS75都更值得入手

接近2.0L車型的售價,與1.8T車型相差無幾的動力表現,CS75的1.5T車型比以往的車型都更能滿足自主品牌SUV消費者日常用車的消費需求。在過往,CS75是一款受到認可的好車。如今,這款好車又再進一步完善了自己。如果那位兼職滴滴快車的車主尚未換車,我想CS75的1.5T車型會在他的購車名單之內的。文末最後,也要感謝一路隨行的長安汽車工程師,其客觀公正的講解屬於自主品牌當中少見的一位。

(從長安工程師身上,能夠看到長安汽車的工科男性格)本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

幸福中國年味新_台中搬家公司

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

大紅燈籠已高高掛起,春節的腳步越來越近。

每年此時,“尋找年味”總能引起人們討論的興緻。“小孩兒小孩兒你別饞,過了臘八就是年;臘八粥,喝幾天,哩哩啦啦二十三;二十三,糖瓜粘;二十四,掃房子;二十五,做豆腐;二十六,去買肉;二十七,宰公雞;二十八,把面發;二十九,蒸饅頭;三十晚上熬一宿;初一、初二滿街走。”快節奏的現代生活里,絕大多數人已很難遵從這樣的老習俗、老規矩來迎接春節,春節的“儀式感”不再如從前那般隆重,人們難免感慨年味淡了。

事實上,作為中國人最重要的節日,春節在人們心目中的重要性從未減弱半分。在鄉村,老一輩人遵循着傳統,掃房子、蒸包子、祭灶神,這是傳統的年味;在城市,離鄉在外的遊子們動動手指,網購各式各樣的年貨送到家鄉的父母身邊,大街上、地鐵站,拉着行李箱的人,大包小裹,滿載而歸。城市的空氣里飄散着期盼、激動、興奮,回家過年,幸福團圓,這是現代的年味。

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

時代在變,生活在變,年味也隨之閃耀着新的光芒,它以更新更多元的方式出現在我們身邊。與過去相比,除了吃穿用等方面的物質年貨,人們還可以選擇豐富多彩的“精神年貨”,為自己的小日子增添更多的幸福感。

進博物館、圖書館、電影院成為一道道文化大餐,被更多的人納進春節“菜單”。春節期間的景區景點也被賦予了類似春晚、團年飯一樣的意義。隨着中國人收入的增長、中國護照“含金量”的提高,春節期間走出家門看世界,成為大眾的新選擇。攜程發布的《2020春節“中國人旅遊過年”趨勢預測報告》显示,“旅遊過年”正成為中國人當下最流行的生活方式,預計今年春節假期出遊人次將再創新高,達到4.5億人次,他們將共同構成一幅流動中國的溫馨圖畫。有專家指出,2020年,旅遊度假將成為中國人的“生活必需品”和首選的消費方式之一,將是中國人“美好生活”的標誌。

這些都是行進中的中國的新年味。這幸福多彩的新年味,得益於中國經濟、社會、文化、民生等各方面綜合實力的不斷提高,中國人自豪地享受改革發展的紅利。

春節是中華民族共同的文化基因,它連接着舊歲與新年,也連接着傳統與現代,展示着中國不斷髮展富強的壯麗畫卷。尋找年味是中國人永恆的話題,在幸福的年味中,我們可以感知中國發展的步伐、聆聽時代前進的足音。

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

《中國的寶藏》:告訴你一個可親可感的中華文化_網頁設計

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

兼具真實性與藝術性的紀錄片,既是我們了解傳統文化的一面鏡子,也是我們探索現代文明的知識寶庫。前不久熱播的紀錄片《中國的寶藏》呈現了中國頂級文物,將“過去”與“現在”聯繫起來,為觀眾提供了一個了解傳統和現代中國的窗口。節目邀請英國節目主持人阿拉斯泰爾·蘇克帶領觀眾踏上博物館尋寶之旅,他對中國歷史十分着迷,正如他在節目中所說:“驚奇於她的美和數千年文明的深度……如果不了解中國的過去,就不可能真正了解今天的中國。”

值得一提的是,《中國的寶藏》英文版已於2019年10月在BBC世界新聞頻道面向全球首播,索福瑞媒介研究有限責任公司對印度、澳大利亞和美國三個國家的觀眾進行了調查,其中有近9成的觀眾表示“我很喜歡紀錄片《中國的寶藏》”,還有觀眾認為這部紀錄片“直觀展示了中國文物的美與獨特”和“深入展現了中國歷史文化的博大精深”。

中英合拍讓中國文物走向世界

由中央廣播電視總台影視劇紀錄片中心出品,央視紀錄國際傳媒有限公司以國際合拍、合作的方式,與BBC世界新聞頻道、英國野馬製作公司共同製作的紀錄片《中國的寶藏》,高水準、藝術化地呈現了故宮博物院等8家博物館、良渚遺址等5個世界文化遺產,包括大禹治水圖玉山、趙孟頫《秀石疏林圖》、大克鼎、章懷太子墓壁畫《客使圖》、兵馬俑等20多件中國頂級文物,為海外觀眾繪製了一幅底蘊深厚的中國歷史文化圖卷。

眾所周知,通暢的傳播渠道對紀錄片到達受眾至關重要,否則,即使有優質的內容,紀錄片也將面臨“傳而不通”的困境。中央廣播電視總台影視劇紀錄片中心召集人庄殿君介紹,“國際合拍模式不僅為中國紀錄片注入了活力,激活了國際合作夥伴的思路,也為中國和世界紀錄片行業的互動提供了更直接的渠道,形成了傳播合力”。

在中英文版本的內容策劃和排播策略上,央視紀錄頻道和BBC世界新聞頻道都充分考慮了海內外觀眾觀看此類節目的需求。據相關數據显示,央視紀錄頻道全國覆蓋3.06億戶家庭觀眾,是中國紀錄片傳播的核心平台,BBC世界新聞頻道全球覆蓋4.65億戶家庭觀眾,促進節目更為廣泛的傳播,激發世界各地的人們對古老而又現代的中國的興趣,加快推動中國文物走向世界的步伐。

央視紀錄頻道副總監史岩表示:“此次首次與BBC世界新聞頻道的合作非常成功,希望未來共同努力,找到中國觀眾和海外觀眾共同感興趣的題材,聯合攝製,共同播出。”BBC世界新聞頻道專題部責任編輯艾瑪·迪亞斯也認同史岩的觀點,她希望以《中國的寶藏》為起點,未來尋求更多題材的項目合作,為全球觀眾提供豐富多彩的節目內容。

描繪文明傳承的力量

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

紀錄片《中國的寶藏》共6集,每集25分鐘,呈現現代中國生活的多個側面,分別是家族、藝術、都會、製造、科技和飲食。每集都從館藏文物珍品切入,通過對傳統的深入解釋,試圖從中國人的現代生活中捕捉到迴響,尋找古代文明的現代傳承。在《中國的寶藏》中,阿拉斯泰爾·蘇克通過拜訪中國各大頂尖博物館以及傾聽各行各業的人們的故事,深切感受中國的歷史悠久和幅員遼闊。

在中國傳媒大學中國紀錄片研究中心研究人員李寧看來,“《中國的寶藏》獲得海內外觀眾認同的另一個原因,就是它講述的婚喪嫁娶、生老病死這些內容,都是跨越地域與種族的共同人生命題。從人類學與社會學的角度來看,這些情感與行為最容易獲得跨文化的理解和認同。所以這部紀錄片不光是講述文物的故事,還在現代生活中找到了它所承載的歷史文化信息的延續”。

《中國的寶藏》中方製片人陳丹丹介紹:“在文物的選擇和主題確定上,我們把握兩個原則。一個是時間跨度,我們選擇的是那些既展現了文明起源,又比較接地氣的文物。另一個原則就是主題必須能夠引起人類情感共鳴。比如你如何吃飯、你的家庭是什麼樣的、你的孩子是怎麼教育的、你如何說話寫字等。因為這些主題是不論哪個國家的受眾都會關心的話題。”

如在第六集《飲食的藝術》中,通過文物深度挖掘中國人的飲食文化,主持人帶領觀眾看到了蘊藏在中國文化深處的遺傳密碼。而飲食,不僅是味道的品鑒,更是一種文化的傳承,其背後強大的認同感,跨越歷史刻度描繪出了文明傳承的力量。

用中國寶藏彰顯文化自信

文化是一個國家和民族的精神與靈魂,文化自信是一個國家、一個民族發展中更基本、更深沉、更持久的力量。作為一種跨文化、跨時空的媒介形態,紀錄片是“講好中國故事,傳播中國聲音”的重要載體,是推動中國文化走出去、促進文化交流與溝通的有效途徑。鑒於此,中國积極推動紀錄片在海外的傳播,中宣部(國新辦)專門設立了“紀錄中國”傳播工程,鼓勵紀錄片製作機構創製精品內容,對外展示中國魅力、推介中國形象、傳播中國文化。作為中宣部(國新辦)“紀錄中國”傳播工程支持項目,《中國的寶藏》一經推出,既彰顯了中國的文化自信,也提供了“讓世界了解中國,讓中國走向世界”的國際表達。節目用講故事的方式,將中國文化融於古往今來中國人的日常生活、喜怒哀樂和拼搏奮鬥中,讓觀眾領略到中國藝術開放包容的境界和博採眾長的氣度,感受到悠久歷史對現代中國的深刻影響,展現了一個傳統與現代交匯融合的立體中國。

同時,作為一部國際合拍紀錄片,《中國的寶藏》借鑒“他者”講故事的方式,用介入方式觸摸真實。主持人阿拉斯泰爾·蘇克擔任觸摸者角色,虛心求學於各大博物館研究員身側,搭乘中國高鐵等交通工具,前往湖南廖家宗祠參加清明祭祀活動、在北京潘家園舊貨市場閑逛、在西安的路邊攤吃烤串、在長沙品嘗滿月酒席上象徵好運的紅雞蛋……一系列身臨其境的觸摸體驗,為紀錄片帶來強烈的現場感、參與感和真實感,有利於中國故事的國際化傳播。節目也在不知不覺中回望了各自的歷史和現在,分享發展經驗,實現構建人類命運共同體的目標。

此外,這部紀錄片的意義並不在於誇大自己的文明獨樹一幟,它的基本精神在於在合作中實現了平等的溝通和對話。正如中國駐英國大使劉曉明所說:“《中國的寶藏》通過介紹中國文物,讓世界認識中國從哪裡來、向何處去,從而使各國人民更加深刻地感知中國、了解中國、領悟中國。這部紀錄片體現了中英兩國人民對美好生活的共同嚮往、對文明交流互鑒的共同追求。”

(記者 牛夢笛 通訊員 游歡)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

文物里的唐都長安人生活_貨運

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

  展出的文官俑。

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

  参觀者在觀展。

  近日,陝西西安博物院“樂居長安—唐都長安人的生活”展開展。本次展覽從西安博物院11萬餘件館藏文物中,特別遴選出280餘件/組唐代精品文物,分別圍繞唐長安的“城、人、衣、食、行、娛”等內容,對唐都長安人的生活進行了全面的再現和闡釋。新華社記者 劉瀟攝

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

盛世修典,築起民間文化長城_網頁設計公司

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

  民間藝人在進行藝術表演。資料圖片

  中國民間文學大系出版工程首批成果——《中國民間文學大系》12個示範卷 資料圖片

  2019年12月25日,中國民間文學大系出版工程(以下簡稱“大系出版工程”)首批成果發布會在人民大會堂舉行,發布了《中國民間文學大系》(以下簡稱《大系》)的12個示範卷,涉及神話、史詩、傳說、故事、歌謠、長詩、說唱、小戲、諺語、謎語、俗語和理論12個門類,共計1200多萬字。作為大系出版工程的成果,《大系》文庫是我國有史以來記錄民間文學數量最多、內容最豐富、種類最齊全、形式最多樣、最具活態性的文庫。

  最大規模的民間文學出版工程

  2017年1月,中辦國辦印發《關於實施中華優秀傳統文化傳承發展工程的意見》。作為《關於實施中華優秀傳統文化傳承發展工程的意見》的15個重點工程之一,大系出版工程在中宣部、中國文聯的領導下,由中國民間文藝家協會團結民間文學領域的專家學者具體實施。

  《大系》涉及神話、史詩、傳說、故事、歌謠、長詩、說唱、小戲、諺語、謎語、俗語、理論12大門類。首批出版的12個示範卷各門類分別一本,每本100萬字左右,共計1200多萬字、300餘幅圖片。12個示範卷分別為《神話·雲南卷(一)》《史詩·黑龍江卷·伊瑪堪分卷》《傳說·吉林卷(一)》《故事·河南卷·平頂山分卷》《歌謠·四川卷·漢族分卷》《長詩·雲南卷(一)》《說唱·遼寧卷(一)》《小戲·湖南卷·影戲分卷》《諺語·河北卷》《謎語·河南卷(一)》《俗語·江蘇卷(一)》《理論(2000—2018)·第一卷(總論)》。

  《大系》所收作品按照科學性、廣泛性、地域性、代表性的原則編選,在田野普查、文字記錄、圖片拍攝和音頻視頻等信息採集以及查閱大量歷史資料的基礎上,強調學術規範,把握民間文學的“活態性、生活性、歷史性和文化性”,注重《大系》內容的全面性、代表性、真實性,多維度、多向度、全方位展現了民間文學的歷史風貌與新時代人文精神。

  示範卷在內容、形式、類型等方面力求反映出民族風格和文化底蘊。比如,《長詩·雲南卷(一)》編選了彝、白、哈尼、傣、壯、苗、傈僳、拉祜、納西、瑤、藏、基諾等12個民族的30部反映婚姻愛情的敘事長詩,這些作品大多採集於20世紀五六十年代,演唱者多為少數民族歌手和民間藝人,並且首次將《宛納帕麗》《南波冠》《葫蘆信》校正為傣族“三大愛情悲劇”;《傳說·吉林卷(一)》中的180餘篇作品,均取自原始採集的資料,在文本規範上進行了重新梳理並增加註釋,盡可能地還原吉林地方文化特色和民間韻味,其中的人蔘傳說、漁獵傳說、淘金傳說和木幫傳說等都是吉林省的特色文化。

  《大系》文庫既有精緻的傳統紙媒產品,也在書中以二維碼的形式鏈接相關民間文學音視頻,拓展了紙質書的內容維度,從而演示活態傳承樣本。比如,在《史詩·黑龍江卷·伊瑪堪分卷》中,讀者可以通過視頻欣賞赫哲語說唱,了解被聯合國教科文組織列為“急需保護的非物質文化遺產名錄”的赫哲族古代部落時期關於征戰、遷徙、社會、生活等英雄史詩;《小戲·湖南卷·影戲分卷》收錄了“儀式性”“非儀式性”劇本及“混合本”135個,建立了視頻資料庫,以最大程度保留和還原各區縣小戲的地方韻味,並通過地域腔調延續歷史文脈。

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

  續存民族文化的集體記憶

  盛世修典是我國自古以來的文化傳統,從《詩經》到《樂府》,從《史記》到《四庫全書》,都為中華民族文化的延續作出了獨特貢獻。編纂出版民間文學大系的意義在於續存民族文化的集體記憶,傳承民族發展的文化基因,並努力實現從立檔存志、強基固本到實現中華優秀傳統文化創造性轉化、創新性發展的銜接和提升,進而築牢中華文化共同體。

  此次出版的《大系》“神話卷”,透過“神話中國”的視角展現了中華文明的構成;“史詩卷”中代代相傳的民族史詩,蘊含珍貴的集體記憶,也是民族語言的歷史文本;“傳說卷”里豐富的民間敘事,包含民間的價值理想、生活哲學;“故事卷”里的民間故事在變化的語境中呈現了歷史經驗、文化律動中永恆不變的存在;“歌謠卷”展現了生活之歌、自然之歌,是精神情感的記錄,也是中華民族語言的瑰寶;“長詩卷”在深入發掘和打撈的過程中萃取經典,體現了長詩佳作的魅力;“說唱卷”作品中樸實的語言、真摯的情感、鮮明的個性,展示了說唱文學演繹故事、塑造典型、表達心靈乃至揭示人性的力量;“小戲卷”讓人們進一步認識和體會民間小戲的審美品格、文化價值;“諺語卷”是文學樣式、文化現象的綜合呈現,短小精悍且充滿了生產生活的智慧;“謎語卷”全面展示了跟謎語相關的文化景觀,許多資料難得而又珍貴;“俗語卷”的作品反映民俗生活,具有地方風情,是對民間口頭語言的發掘梳理和研究;“理論卷”是21世紀以來我國民間文學界第一次對最新的理論研究成果進行大規模收集、整理、編纂、回顧。總之,口耳相傳的民間文學既是民族文化的活化石,又是一部發展中的民族生活史、文化史、思想史,聯繫着民族文化的源頭並指向廣闊的未來。

  文學總體上分為兩種:一種是個人用文字創作的,以書面傳播的文學;另一種是民間集體口頭創作的,口口相傳的民間文學。後者是前者的源頭,是根性的文學。中國民間文學大系,強調文學的民間性,反映的是中國社會生活的面貌。從某種程度上說,民間文學大系就是我們民間生活的百科全書,包含民俗學、歷史學、藝術學等學科內容,蘊含豐富的史料細節,可以為民族學、民俗學的研究提供基礎性資料和基本理論,可以作為人文社會研究的基礎文獻,也可以作為教材的資料基礎,有助於生動傳承民族文化,增強中華民族的文化認同感和凝聚力。

  民間文學研究整理的總動員

  中國民間文學大系出版工程是在中國民間文藝家協會70年文獻積累的基礎上實施的。中國民協的前身是成立於1950年的中國民間文學研究會,70年來民間文學一直是其關注重點。新中國成立以來,中國民協(包括其前身“民研會”)開展了3次大規模的民間文學搶救性調查、收集、整理工作,這包括1957年的民歌調查運動、20世紀80年代的中國民間文學“三套集成”(《中國民間故事集成》《中國歌謠集成》《中國諺語集成》)普查編纂工作和始自2002年的中國民間文化遺產搶救工程。

  在數十年採集整理民間文學資料的基礎上,中國民協組織實施中國民間文學大系出版工程,進一步對“中國口頭文學遺產数字化工程”数字化搶救和整理的11000餘冊、約18億字資料進行研究、整理和編纂,並補充和完善新世紀以來的民間文學作品。

  中國民間文學大系出版工程啟動之初,我們便成立了“大系出版工程”學術委員會、編纂出版工作委員會及12個編輯專家組,以把握民間文學的實質,尊重民間文學的規律,保障編纂出版的質量和水平。工程的實施以中國民協為主,同時各級民協上下聯動,充分調動高等院校、科研院所及有關部門和機構參与的积極性,團結全國各地近千名專家學者參与編纂,凝聚了一批民間文學的專家學者和愛好者,培養了一批有能力有擔當的民間文學梯隊人才。

  大系出版工程從啟動伊始就確立了“示範帶動”的方法。一方面,在具有突出優勢的省區市部署共計55個示範卷的編纂任務;另一方面,形成了《〈中國民間文學大系〉編纂工作規範及實施辦法》《中國民間文學大系授權書》《中國民間文學大系出版工程編纂出版工作流程和相關職責》《中國民間文學大系辦公室工作分工》《中國民間文學大系出版工程相關簡稱使用規範》等系統的工作規程,以保證各項工作科學規範開展。

  《大系》編纂過程中嚴守學術規範,尊重民間文化的發展規律,關注民間文學的“活態性、生活性、歷史性和文化性”,注重大系的全面性、代表性、真實性。同時,我們還不間斷地開展研討,舉辦培訓講座,僅2018年8月以來,就在各省區市召開示範卷編纂工作啟動會、座談會、研討會20餘次,保證了《大系》內容的學術性、專業性。

  經過近三年的辛勤工作,大系出版工程取得了顯著成果。截至2019年12月17日,全國共有134卷啟動了編纂工作,其中12個示範卷已經面世,還有34卷已進入審稿、修改階段,1卷已進入出版社編校環節,其餘卷本正在補充和修改。根據規劃,大系出版工程將在2025年前出版《中國民間文學大系》大型文庫,建成电子文獻數據庫,同時開發一批經典讀本、實用讀本、普及讀本和對外宣傳推介產品和衍生產品。

  (作者:潘魯生,系中國文聯副主席、中國民間文藝家協會主席、中國民間文學大系出版工程編纂出版工作委員會主任)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

Apple Watch 才在台灣開放 ECG 心電圖功能,就有醫師分享幫病患找出心臟問題的實例_網頁設計公司

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

「越早電燒或是冷凍氣球治療,未來維持正常心律的機會越大,併發症越少」這是林中行醫師分享所引述的近期研究結果。不過從他所分享多年為莫名心悸所苦,做了多次 24 小時心電圖卻又無法找到問題的病例來看。以往這名病患可能得要再花更多時間才可能找到心房顫動徵狀的狀況,現在透過最近針對 Series 4(不包括 SE)以上 Apple Watch 開放的 ECG 心電圖功能在台灣開放之後。

這位 7、8 年來始終無法診斷出心臟問題的病患,透過 Apple Watch ECG 功能找到心臟狀況並儘速啟動治療的案例。繼續閱讀 Apple Watch 才在台灣開放 ECG 心電圖功能,就有醫師分享幫病患找出心臟問題的實例報導內文。

Apple Watch 才在台灣開放 ECG 心電圖功能,就有醫師分享幫病患找出心臟問題的實例

儘管國外早已有不少透過具備心電圖功能的 Apple Watch,即時測量發現心臟異常的案例。但從 Series 4 開賣等到 2020 年底才正式開放的這個 ECG 心電圖功能,在不到半個月的時間裡,想不到很快就為 7、8 年來始終懷疑自己心悸頭暈可能是心臟問題的蔡先生確認了病徵。
 
時常在社群媒體分享心臟相關知識與案例的林中行醫師,最近分享了一個透過 Apple Watch 心電圖功能確認心房顫動狀況的病患。說真的,用這樣的功能找出問題並不是什麼新聞了,但這位蔡先生的案例其實主要是在於,儘管他自己有察覺不適,但在這些年的時間歷經了 4、5 次的 24 小時心電圖檢查,卻始終無法在診斷期間發現異常。

▲圖片來源:Apple

這也使其多年來都為此所苦 — 不僅是身體不適而已,也包括了找不到病因的不安因素吧?再加上現在普遍的觀念是越早進行相關治療,未來維持正常心律的機會越大且併發症越少的關係,更讓人會希望能儘早發現狀況。

這次的案例,就是讓蔡先生透過 Apple Watch 在疑似發作的時候即時啟動 Apple Watch ECG 的偵測,最終也將輸出的 PDF 心電圖資料與林醫師討論後,確認為心房顫動並將盡快接受電燒治療。

這次的病例,林醫師也分享了 Apple Watch 產生的心電圖資料的判讀心得,認為「圖型真的清楚,實為診斷的一大利器…」是說,經過了這麼久的認證終於放行的這個功能,現在更透過這樣專業的醫師分享的案例讓人知道這類隨手可得的科技產品,也讓人開始知道,這是真的是有可能為輔助醫療用途幫上忙的。也的確如醫師所說,是心律不整病人的福音。

更多資訊:
· Apple Watch 蘋果官網
· 心房顫動相關知識

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

延伸閱讀:

HomePod mini 開箱體驗:一顆就能敲開蘋果智慧家門,兩顆更是不嫌多

Porsche Taycan 4S 生活試駕體驗:它沒有瘋狂模式,你有

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。