Browse Source

更新demo案例

杨充 4 years ago
parent
commit
0ef26bec60
31 changed files with 2066 additions and 2877 deletions
  1. 1 1
      Demo/build.gradle
  2. 1 1
      Demo/src/main/AndroidManifest.xml
  3. 3 0
      Demo/src/main/java/org/yczbj/ycvideoplayer/BaseApplication.java
  4. 2 2
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/activity/TypeActivity.java
  5. 5 1
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/ContinuousVideoActivity.java
  6. 1 1
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/RecyclerViewFragment.java
  7. 2 0
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiktok/TikTok2Activity.java
  8. 4 4
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiktok/TikTokRenderView.java
  9. 2 2
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiktok/TikTokRenderViewFactory.java
  10. 0 2788
      Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiktok/VerticalViewPager.java
  11. 1 1
      Demo/src/main/res/layout/activity_tiktok2.xml
  12. 2 7
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/controller/BaseVideoController.java
  13. 7 4
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/player/AudioFocusHelper.java
  14. 2 2
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/player/VideoPlayer.java
  15. 1 1
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/InterSurfaceView.java
  16. 18 1
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/MeasureHelper.java
  17. 11 2
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/RenderSurfaceView.java
  18. 59 46
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/RenderTextureView.java
  19. 1 1
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/SurfaceFactory.java
  20. 3 2
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/SurfaceViewFactory.java
  21. 2 1
      VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/TextureViewFactory.java
  22. 13 2
      read/04.视频播放器封装思路.md
  23. 147 3
      read/11.视频播放器音频焦点抢占.md
  24. 0 0
      read/24.1SurfaceView深入学习.md
  25. 515 0
      read/24.2SurfaceView源码分析.md
  26. 4 2
      read/25.TextureView深入学习.md
  27. 94 0
      read/26.视频编码和解码学习.md
  28. 2 2
      read/29.视频播放器埋点监听.md
  29. 171 0
      read/30.音视频问题考点.md
  30. 808 0
      read/34.音频播放锁屏分析.md
  31. 184 0
      read/35.耳机控制音视频音量.md

+ 1 - 1
Demo/build.gradle

@@ -59,7 +59,7 @@ dependencies {
     implementation 'com.github.ctiao:ndkbitmap-armv7a:0.9.21'
     implementation 'com.github.ctiao:ndkbitmap-armv7a:0.9.21'
 
 
     //自己封装的库,都有对应的案例项目【欢迎star】:https://github.com/yangchong211
     //自己封装的库,都有对应的案例项目【欢迎star】:https://github.com/yangchong211
-    implementation 'com.yc:PagerLib:1.0.1'
+    implementation 'com.yc:PagerLib:1.0.4'
     implementation 'cn.yc:YCStateLib:1.2.2'                                  //状态管理
     implementation 'cn.yc:YCStateLib:1.2.2'                                  //状态管理
     implementation project(':VideoCache')
     implementation project(':VideoCache')
     implementation project(':VideoPlayer')
     implementation project(':VideoPlayer')

+ 1 - 1
Demo/src/main/AndroidManifest.xml

@@ -86,7 +86,7 @@
         <activity android:name=".newPlayer.ad.AdActivity"
         <activity android:name=".newPlayer.ad.AdActivity"
             android:configChanges="orientation|keyboardHidden|screenSize"
             android:configChanges="orientation|keyboardHidden|screenSize"
             android:screenOrientation="portrait"/>
             android:screenOrientation="portrait"/>
-        <activity android:name=".newPlayer.list.ListVideoActivity"
+        <activity android:name=".newPlayer.list.ContinuousVideoActivity"
             android:configChanges="orientation|keyboardHidden|screenSize"
             android:configChanges="orientation|keyboardHidden|screenSize"
             android:screenOrientation="portrait"/>
             android:screenOrientation="portrait"/>
         <activity android:name=".newPlayer.clarity.ClarityActivity"
         <activity android:name=".newPlayer.clarity.ClarityActivity"

+ 3 - 0
Demo/src/main/java/org/yczbj/ycvideoplayer/BaseApplication.java

@@ -12,6 +12,7 @@ import com.yc.kernel.utils.PlayerFactoryUtils;
 
 
 import org.yczbj.ycvideoplayerlib.config.VideoPlayerConfig;
 import org.yczbj.ycvideoplayerlib.config.VideoPlayerConfig;
 import org.yczbj.ycvideoplayerlib.player.VideoViewManager;
 import org.yczbj.ycvideoplayerlib.player.VideoViewManager;
+import org.yczbj.ycvideoplayerlib.surface.SurfaceViewFactory;
 
 
 /**
 /**
  * ================================================
  * ================================================
@@ -66,6 +67,8 @@ public class BaseApplication extends Application {
                 .setLogEnabled(true)
                 .setLogEnabled(true)
                 //设置ijk
                 //设置ijk
                 .setPlayerFactory(player)
                 .setPlayerFactory(player)
+                //创建SurfaceView
+                //.setRenderViewFactory(SurfaceViewFactory.create())
                 .build());
                 .build());
     }
     }
 
 

+ 2 - 2
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/activity/TypeActivity.java

@@ -14,7 +14,7 @@ import org.yczbj.ycvideoplayer.R;
 import org.yczbj.ycvideoplayer.newPlayer.ad.AdActivity;
 import org.yczbj.ycvideoplayer.newPlayer.ad.AdActivity;
 import org.yczbj.ycvideoplayer.newPlayer.clarity.ClarityActivity;
 import org.yczbj.ycvideoplayer.newPlayer.clarity.ClarityActivity;
 import org.yczbj.ycvideoplayer.newPlayer.danmu.DanmuActivity;
 import org.yczbj.ycvideoplayer.newPlayer.danmu.DanmuActivity;
-import org.yczbj.ycvideoplayer.newPlayer.list.ListVideoActivity;
+import org.yczbj.ycvideoplayer.newPlayer.list.ContinuousVideoActivity;
 import org.yczbj.ycvideoplayer.newPlayer.list.TestListActivity;
 import org.yczbj.ycvideoplayer.newPlayer.list.TestListActivity;
 import org.yczbj.ycvideoplayer.newPlayer.pip.PipActivity;
 import org.yczbj.ycvideoplayer.newPlayer.pip.PipActivity;
 import org.yczbj.ycvideoplayer.newPlayer.pip.PipListActivity;
 import org.yczbj.ycvideoplayer.newPlayer.pip.PipListActivity;
@@ -200,7 +200,7 @@ public class TypeActivity extends AppCompatActivity implements View.OnClickListe
         } else if (v == mTv81){
         } else if (v == mTv81){
             startActivity(new Intent(this, AdActivity.class));
             startActivity(new Intent(this, AdActivity.class));
         } else if (v == mTv101){
         } else if (v == mTv101){
-            startActivity(new Intent(this, ListVideoActivity.class));
+            startActivity(new Intent(this, ContinuousVideoActivity.class));
         } else if (v == mTv111){
         } else if (v == mTv111){
             startActivity(new Intent(this, ClarityActivity.class));
             startActivity(new Intent(this, ClarityActivity.class));
         } else if (v == mTv131){
         } else if (v == mTv131){

+ 5 - 1
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/ListVideoActivity.java → Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/ContinuousVideoActivity.java

@@ -19,7 +19,11 @@ import org.yczbj.ycvideoplayerlib.ui.view.BasisVideoController;
 
 
 import java.util.List;
 import java.util.List;
 
 
-public class ListVideoActivity extends AppCompatActivity implements View.OnClickListener {
+/**
+ * 连续播放列表视频
+ * 意思是说播放完了第一个,接着播放第二个,第三个……
+ */
+public class ContinuousVideoActivity extends AppCompatActivity implements View.OnClickListener {
 
 
     private VideoPlayer mVideoPlayer;
     private VideoPlayer mVideoPlayer;
     private Button mBtnScaleNormal;
     private Button mBtnScaleNormal;

+ 1 - 1
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/list/RecyclerViewFragment.java

@@ -30,7 +30,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
- * RecyclerView demo
+ * 普通的列表播放
  */
  */
 public class RecyclerViewFragment extends Fragment {
 public class RecyclerViewFragment extends Fragment {
 
 

+ 2 - 0
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiktok/TikTok2Activity.java

@@ -16,6 +16,8 @@ import com.yc.kernel.utils.VideoLogUtils;
 
 
 import org.yczbj.ycvideoplayer.ConstantVideo;
 import org.yczbj.ycvideoplayer.ConstantVideo;
 import org.yczbj.ycvideoplayer.R;
 import org.yczbj.ycvideoplayer.R;
+
+import com.yc.pagerlib.pager.VerticalViewPager;
 import com.yc.videocache.cache.PreloadManager;
 import com.yc.videocache.cache.PreloadManager;
 import com.yc.videocache.cache.ProxyVideoCacheManager;
 import com.yc.videocache.cache.ProxyVideoCacheManager;
 
 

+ 4 - 4
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiktok/TikTokRenderView.java

@@ -8,18 +8,18 @@ import androidx.annotation.NonNull;
 import com.yc.kernel.inter.AbstractVideoPlayer;
 import com.yc.kernel.inter.AbstractVideoPlayer;
 
 
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
-import org.yczbj.ycvideoplayerlib.surface.ISurfaceView;
+import org.yczbj.ycvideoplayerlib.surface.InterSurfaceView;
 
 
 
 
 /**
 /**
  * TikTok专用RenderView,横屏视频默认显示,竖屏视频居中裁剪
  * TikTok专用RenderView,横屏视频默认显示,竖屏视频居中裁剪
  * 使用代理模式实现
  * 使用代理模式实现
  */
  */
-public class TikTokRenderView implements ISurfaceView {
+public class TikTokRenderView implements InterSurfaceView {
 
 
-    private ISurfaceView mProxyRenderView;
+    private InterSurfaceView mProxyRenderView;
 
 
-    TikTokRenderView(@NonNull ISurfaceView renderView) {
+    TikTokRenderView(@NonNull InterSurfaceView renderView) {
         this.mProxyRenderView = renderView;
         this.mProxyRenderView = renderView;
     }
     }
 
 

+ 2 - 2
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiktok/TikTokRenderViewFactory.java

@@ -2,7 +2,7 @@ package org.yczbj.ycvideoplayer.newPlayer.tiktok;
 
 
 import android.content.Context;
 import android.content.Context;
 
 
-import org.yczbj.ycvideoplayerlib.surface.ISurfaceView;
+import org.yczbj.ycvideoplayerlib.surface.InterSurfaceView;
 import org.yczbj.ycvideoplayerlib.surface.SurfaceFactory;
 import org.yczbj.ycvideoplayerlib.surface.SurfaceFactory;
 import org.yczbj.ycvideoplayerlib.surface.RenderTextureView;
 import org.yczbj.ycvideoplayerlib.surface.RenderTextureView;
 
 
@@ -14,7 +14,7 @@ public class TikTokRenderViewFactory extends SurfaceFactory {
     }
     }
 
 
     @Override
     @Override
-    public ISurfaceView createRenderView(Context context) {
+    public InterSurfaceView createRenderView(Context context) {
         return new TikTokRenderView(new RenderTextureView(context));
         return new TikTokRenderView(new RenderTextureView(context));
     }
     }
 }
 }

+ 0 - 2788
Demo/src/main/java/org/yczbj/ycvideoplayer/newPlayer/tiktok/VerticalViewPager.java

@@ -1,2788 +0,0 @@
-package org.yczbj.ycvideoplayer.newPlayer.tiktok;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.database.DataSetObserver;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.FocusFinder;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.SoundEffectConstants;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.Interpolator;
-import android.widget.Scroller;
-
-import androidx.core.os.ParcelableCompat;
-import androidx.core.os.ParcelableCompatCreatorCallbacks;
-import androidx.core.view.AccessibilityDelegateCompat;
-import androidx.core.view.MotionEventCompat;
-import androidx.core.view.VelocityTrackerCompat;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.ViewConfigurationCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.core.view.accessibility.AccessibilityRecordCompat;
-import androidx.core.widget.EdgeEffectCompat;
-import androidx.viewpager.widget.PagerAdapter;
-import androidx.viewpager.widget.ViewPager;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-
-/**
- * Created by castorflex on 12/29/13.
- * Just a copy of the original ViewPager modified to support vertical Scrolling
- */
-public class VerticalViewPager extends ViewGroup {
-
-    private static final String TAG = "ViewPager";
-    private static final boolean DEBUG = false;
-
-    private static final boolean USE_CACHE = false;
-
-    private static final int DEFAULT_OFFSCREEN_PAGES = 1;
-    private static final int MAX_SETTLE_DURATION = 600; // ms
-    private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
-
-    private static final int DEFAULT_GUTTER_SIZE = 16; // dips
-
-    private static final int MIN_FLING_VELOCITY = 400; // dips
-
-    private final int TYPE_VIEW_SCROLLED = 4096;
-
-    private static final int[] LAYOUT_ATTRS = new int[]{
-            android.R.attr.layout_gravity
-    };
-
-    /**
-     * Used to track what the expected number of items in the adapter should be.
-     * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
-     */
-    private int mExpectedAdapterCount;
-
-    static class ItemInfo {
-        Object object;
-        int position;
-        boolean scrolling;
-        float heightFactor;
-        float offset;
-    }
-
-    private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {
-        @Override
-        public int compare(ItemInfo lhs, ItemInfo rhs) {
-            return lhs.position - rhs.position;
-        }
-    };
-
-    private static final Interpolator sInterpolator = new Interpolator() {
-        public float getInterpolation(float t) {
-            t -= 1.0f;
-            return t * t * t * t * t + 1.0f;
-        }
-    };
-
-    private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
-    private final ItemInfo mTempItem = new ItemInfo();
-
-    private final Rect mTempRect = new Rect();
-
-    private PagerAdapter mAdapter;
-    private int mCurItem;   // Index of currently displayed page.
-    private int mRestoredCurItem = -1;
-    private Parcelable mRestoredAdapterState = null;
-    private ClassLoader mRestoredClassLoader = null;
-    private Scroller mScroller;
-    private PagerObserver mObserver;
-
-    private int mPageMargin;
-    private Drawable mMarginDrawable;
-    private int mLeftPageBounds;
-    private int mRightPageBounds;
-
-    // Offsets of the first and last items, if known.
-    // Set during population, used to determine if we are at the beginning
-    // or end of the pager data set during touch scrolling.
-    private float mFirstOffset = -Float.MAX_VALUE;
-    private float mLastOffset = Float.MAX_VALUE;
-
-    private int mChildWidthMeasureSpec;
-    private int mChildHeightMeasureSpec;
-    private boolean mInLayout;
-
-    private boolean mScrollingCacheEnabled;
-
-    private boolean mPopulatePending;
-    private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
-
-    private boolean mIsBeingDragged;
-    private boolean mIsUnableToDrag;
-    private boolean mIgnoreGutter;
-    private int mDefaultGutterSize;
-    private int mGutterSize;
-    private int mTouchSlop;
-    /**
-     * Position of the last motion event.
-     */
-    private float mLastMotionX;
-    private float mLastMotionY;
-    private float mInitialMotionX;
-    private float mInitialMotionY;
-    /**
-     * ID of the active pointer. This is used to retain consistency during
-     * drags/flings if multiple pointers are used.
-     */
-    private int mActivePointerId = INVALID_POINTER;
-    /**
-     * Sentinel value for no current active pointer.
-     * Used by {@link #mActivePointerId}.
-     */
-    private static final int INVALID_POINTER = -1;
-
-    /**
-     * Determines speed during touch scrolling
-     */
-    private VelocityTracker mVelocityTracker;
-    private int mMinimumVelocity;
-    private int mMaximumVelocity;
-    private int mFlingDistance;
-    private int mCloseEnough;
-
-    // If the pager is at least this close to its final position, complete the scroll
-    // on touch down and let the user interact with the content inside instead of
-    // "catching" the flinging pager.
-    private static final int CLOSE_ENOUGH = 2; // dp
-
-    private boolean mFakeDragging;
-    private long mFakeDragBeginTime;
-
-    private EdgeEffectCompat mTopEdge;
-    private EdgeEffectCompat mBottomEdge;
-
-    private boolean mFirstLayout = true;
-    private boolean mNeedCalculatePageOffsets = false;
-    private boolean mCalledSuper;
-    private int mDecorChildCount;
-
-    private ViewPager.OnPageChangeListener mOnPageChangeListener;
-    private ViewPager.OnPageChangeListener mInternalPageChangeListener;
-    private OnAdapterChangeListener mAdapterChangeListener;
-    private ViewPager.PageTransformer mPageTransformer;
-    private Method mSetChildrenDrawingOrderEnabled;
-
-    private static final int DRAW_ORDER_DEFAULT = 0;
-    private static final int DRAW_ORDER_FORWARD = 1;
-    private static final int DRAW_ORDER_REVERSE = 2;
-    private int mDrawingOrder;
-    private ArrayList<View> mDrawingOrderedChildren;
-    private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();
-
-    /**
-     * Indicates that the pager is in an idle, settled state. The current page
-     * is fully in view and no animation is in progress.
-     */
-    public static final int SCROLL_STATE_IDLE = 0;
-
-    /**
-     * Indicates that the pager is currently being dragged by the user.
-     */
-    public static final int SCROLL_STATE_DRAGGING = 1;
-
-    /**
-     * Indicates that the pager is in the process of settling to a final position.
-     */
-    public static final int SCROLL_STATE_SETTLING = 2;
-
-    private final Runnable mEndScrollRunnable = new Runnable() {
-        public void run() {
-            setScrollState(SCROLL_STATE_IDLE);
-            populate();
-        }
-    };
-
-    private int mScrollState = SCROLL_STATE_IDLE;
-
-    /**
-     * Used internally to monitor when adapters are switched.
-     */
-    interface OnAdapterChangeListener {
-        public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
-    }
-
-    /**
-     * Used internally to tag special types of child views that should be added as
-     * pager decorations by default.
-     */
-    interface Decor {
-    }
-
-    public VerticalViewPager(Context context) {
-        super(context);
-        initViewPager();
-    }
-
-    public VerticalViewPager(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        initViewPager();
-    }
-
-    void initViewPager() {
-        setWillNotDraw(false);
-        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
-        setFocusable(true);
-        final Context context = getContext();
-        mScroller = new Scroller(context, sInterpolator);
-        final ViewConfiguration configuration = ViewConfiguration.get(context);
-        final float density = context.getResources().getDisplayMetrics().density;
-
-        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
-        mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
-        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
-        mTopEdge = new EdgeEffectCompat(context);
-        mBottomEdge = new EdgeEffectCompat(context);
-
-        mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
-        mCloseEnough = (int) (CLOSE_ENOUGH * density);
-        mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
-
-        ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());
-
-        if (ViewCompat.getImportantForAccessibility(this)
-                == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
-            ViewCompat.setImportantForAccessibility(this,
-                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        removeCallbacks(mEndScrollRunnable);
-        super.onDetachedFromWindow();
-    }
-
-    private void setScrollState(int newState) {
-        if (mScrollState == newState) {
-            return;
-        }
-
-        mScrollState = newState;
-        if (mPageTransformer != null) {
-            // PageTransformers can do complex things that benefit from hardware layers.
-            enableLayers(newState != SCROLL_STATE_IDLE);
-        }
-        if (mOnPageChangeListener != null) {
-            mOnPageChangeListener.onPageScrollStateChanged(newState);
-        }
-    }
-
-    /**
-     * Set a PagerAdapter that will supply views for this pager as needed.
-     *
-     * @param adapter Adapter to use
-     */
-    public void setAdapter(PagerAdapter adapter) {
-        if (mAdapter != null) {
-            mAdapter.unregisterDataSetObserver(mObserver);
-            mAdapter.startUpdate(this);
-            for (int i = 0; i < mItems.size(); i++) {
-                final ItemInfo ii = mItems.get(i);
-                mAdapter.destroyItem(this, ii.position, ii.object);
-            }
-            mAdapter.finishUpdate(this);
-            mItems.clear();
-            removeNonDecorViews();
-            mCurItem = 0;
-            scrollTo(0, 0);
-        }
-
-        final PagerAdapter oldAdapter = mAdapter;
-        mAdapter = adapter;
-        mExpectedAdapterCount = 0;
-
-        if (mAdapter != null) {
-            if (mObserver == null) {
-                mObserver = new PagerObserver();
-            }
-            mAdapter.registerDataSetObserver(mObserver);
-            mPopulatePending = false;
-            final boolean wasFirstLayout = mFirstLayout;
-            mFirstLayout = true;
-            mExpectedAdapterCount = mAdapter.getCount();
-            if (mRestoredCurItem >= 0) {
-                mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
-                setCurrentItemInternal(mRestoredCurItem, false, true);
-                mRestoredCurItem = -1;
-                mRestoredAdapterState = null;
-                mRestoredClassLoader = null;
-            } else if (!wasFirstLayout) {
-                populate();
-            } else {
-                requestLayout();
-            }
-        }
-
-        if (mAdapterChangeListener != null && oldAdapter != adapter) {
-            mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
-        }
-    }
-
-    private void removeNonDecorViews() {
-        for (int i = 0; i < getChildCount(); i++) {
-            final View child = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            if (!lp.isDecor) {
-                removeViewAt(i);
-                i--;
-            }
-        }
-    }
-
-    /**
-     * Retrieve the current adapter supplying pages.
-     *
-     * @return The currently registered PagerAdapter
-     */
-    public PagerAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
-        mAdapterChangeListener = listener;
-    }
-
-//    private int getClientWidth() {
-//        return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
-//    }
-
-    private int getClientHeight() {
-        return getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
-    }
-
-
-    /**
-     * Set the currently selected page. If the ViewPager has already been through its first
-     * layout with its current adapter there will be a smooth animated transition between
-     * the current item and the specified item.
-     *
-     * @param item Item index to select
-     */
-    public void setCurrentItem(int item) {
-        mPopulatePending = false;
-        setCurrentItemInternal(item, !mFirstLayout, false);
-    }
-
-    /**
-     * Set the currently selected page.
-     *
-     * @param item         Item index to select
-     * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
-     */
-    public void setCurrentItem(int item, boolean smoothScroll) {
-        mPopulatePending = false;
-        setCurrentItemInternal(item, smoothScroll, false);
-    }
-
-    public int getCurrentItem() {
-        return mCurItem;
-    }
-
-    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
-        setCurrentItemInternal(item, smoothScroll, always, 0);
-    }
-
-    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
-        if (mAdapter == null || mAdapter.getCount() <= 0) {
-            setScrollingCacheEnabled(false);
-            return;
-        }
-        if (!always && mCurItem == item && mItems.size() != 0) {
-            setScrollingCacheEnabled(false);
-            return;
-        }
-
-        if (item < 0) {
-            item = 0;
-        } else if (item >= mAdapter.getCount()) {
-            item = mAdapter.getCount() - 1;
-        }
-        final int pageLimit = mOffscreenPageLimit;
-        if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
-            // We are doing a jump by more than one page.  To avoid
-            // glitches, we want to keep all current pages in the view
-            // until the scroll ends.
-            for (int i = 0; i < mItems.size(); i++) {
-                mItems.get(i).scrolling = true;
-            }
-        }
-        final boolean dispatchSelected = mCurItem != item;
-
-        if (mFirstLayout) {
-            // We don't have any idea how big we are yet and shouldn't have any pages either.
-            // Just set things up and let the pending layout handle things.
-            mCurItem = item;
-            if (dispatchSelected && mOnPageChangeListener != null) {
-                mOnPageChangeListener.onPageSelected(item);
-            }
-            if (dispatchSelected && mInternalPageChangeListener != null) {
-                mInternalPageChangeListener.onPageSelected(item);
-            }
-            requestLayout();
-        } else {
-            populate(item);
-            scrollToItem(item, smoothScroll, velocity, dispatchSelected);
-        }
-    }
-
-    private void scrollToItem(int item, boolean smoothScroll, int velocity,
-                              boolean dispatchSelected) {
-        final ItemInfo curInfo = infoForPosition(item);
-        int destY = 0;
-        if (curInfo != null) {
-            final int height = getClientHeight();
-            destY = (int) (height * Math.max(mFirstOffset,
-                    Math.min(curInfo.offset, mLastOffset)));
-        }
-        if (smoothScroll) {
-            smoothScrollTo(0, destY, velocity);
-            if (dispatchSelected && mOnPageChangeListener != null) {
-                mOnPageChangeListener.onPageSelected(item);
-            }
-            if (dispatchSelected && mInternalPageChangeListener != null) {
-                mInternalPageChangeListener.onPageSelected(item);
-            }
-        } else {
-            if (dispatchSelected && mOnPageChangeListener != null) {
-                mOnPageChangeListener.onPageSelected(item);
-            }
-            if (dispatchSelected && mInternalPageChangeListener != null) {
-                mInternalPageChangeListener.onPageSelected(item);
-            }
-            completeScroll(false);
-            scrollTo(0, destY);
-            pageScrolled(destY);
-        }
-    }
-
-    /**
-     * Set a listener that will be invoked whenever the page changes or is incrementally
-     * scrolled. See {@link ViewPager.OnPageChangeListener}.
-     *
-     * @param listener Listener to set
-     */
-    public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
-        mOnPageChangeListener = listener;
-    }
-
-    /**
-     * Set a {@link ViewPager.PageTransformer} that will be called for each attached page whenever
-     * the scroll position is changed. This allows the application to apply custom property
-     * transformations to each page, overriding the default sliding look and feel.
-     * <p/>
-     * <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.
-     * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>
-     *
-     * @param reverseDrawingOrder true if the supplied PageTransformer requires page views
-     *                            to be drawn from last to first instead of first to last.
-     * @param transformer         PageTransformer that will modify each page's animation properties
-     */
-    public void setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer) {
-        if (Build.VERSION.SDK_INT >= 11) {
-            final boolean hasTransformer = transformer != null;
-            final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
-            mPageTransformer = transformer;
-            setChildrenDrawingOrderEnabledCompat(hasTransformer);
-            if (hasTransformer) {
-                mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
-            } else {
-                mDrawingOrder = DRAW_ORDER_DEFAULT;
-            }
-            if (needsPopulate) populate();
-        }
-    }
-
-    void setChildrenDrawingOrderEnabledCompat(boolean enable) {
-        if (Build.VERSION.SDK_INT >= 7) {
-            if (mSetChildrenDrawingOrderEnabled == null) {
-                try {
-                    mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod(
-                            "setChildrenDrawingOrderEnabled", new Class[]{Boolean.TYPE});
-                } catch (NoSuchMethodException e) {
-                    Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);
-                }
-            }
-            try {
-                mSetChildrenDrawingOrderEnabled.invoke(this, enable);
-            } catch (Exception e) {
-                Log.e(TAG, "Error changing children drawing order", e);
-            }
-        }
-    }
-
-    @Override
-    protected int getChildDrawingOrder(int childCount, int i) {
-        final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
-        final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
-        return result;
-    }
-
-    /**
-     * Set a separate OnPageChangeListener for internal use by the support library.
-     *
-     * @param listener Listener to set
-     * @return The old listener that was set, if any.
-     */
-    ViewPager.OnPageChangeListener setInternalPageChangeListener(ViewPager.OnPageChangeListener listener) {
-        ViewPager.OnPageChangeListener oldListener = mInternalPageChangeListener;
-        mInternalPageChangeListener = listener;
-        return oldListener;
-    }
-
-    /**
-     * Returns the number of pages that will be retained to either side of the
-     * current page in the view hierarchy in an idle state. Defaults to 1.
-     *
-     * @return How many pages will be kept offscreen on either side
-     * @see #setOffscreenPageLimit(int)
-     */
-    public int getOffscreenPageLimit() {
-        return mOffscreenPageLimit;
-    }
-
-    /**
-     * Set the number of pages that should be retained to either side of the
-     * current page in the view hierarchy in an idle state. Pages beyond this
-     * limit will be recreated from the adapter when needed.
-     * <p/>
-     * <p>This is offered as an optimization. If you know in advance the number
-     * of pages you will need to support or have lazy-loading mechanisms in place
-     * on your pages, tweaking this setting can have benefits in perceived smoothness
-     * of paging animations and interaction. If you have a small number of pages (3-4)
-     * that you can keep active all at once, less time will be spent in layout for
-     * newly created view subtrees as the user pages back and forth.</p>
-     * <p/>
-     * <p>You should keep this limit low, especially if your pages have complex layouts.
-     * This setting defaults to 1.</p>
-     *
-     * @param limit How many pages will be kept offscreen in an idle state.
-     */
-    public void setOffscreenPageLimit(int limit) {
-        if (limit < DEFAULT_OFFSCREEN_PAGES) {
-            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
-                    DEFAULT_OFFSCREEN_PAGES);
-            limit = DEFAULT_OFFSCREEN_PAGES;
-        }
-        if (limit != mOffscreenPageLimit) {
-            mOffscreenPageLimit = limit;
-            populate();
-        }
-    }
-
-    /**
-     * Set the margin between pages.
-     *
-     * @param marginPixels Distance between adjacent pages in pixels
-     * @see #getPageMargin()
-     * @see #setPageMarginDrawable(Drawable)
-     * @see #setPageMarginDrawable(int)
-     */
-    public void setPageMargin(int marginPixels) {
-        final int oldMargin = mPageMargin;
-        mPageMargin = marginPixels;
-
-        final int height = getHeight();
-        recomputeScrollPosition(height, height, marginPixels, oldMargin);
-
-        requestLayout();
-    }
-
-    /**
-     * Return the margin between pages.
-     *
-     * @return The size of the margin in pixels
-     */
-    public int getPageMargin() {
-        return mPageMargin;
-    }
-
-    /**
-     * Set a drawable that will be used to fill the margin between pages.
-     *
-     * @param d Drawable to display between pages
-     */
-    public void setPageMarginDrawable(Drawable d) {
-        mMarginDrawable = d;
-        if (d != null) refreshDrawableState();
-        setWillNotDraw(d == null);
-        invalidate();
-    }
-
-    /**
-     * Set a drawable that will be used to fill the margin between pages.
-     *
-     * @param resId Resource ID of a drawable to display between pages
-     */
-    public void setPageMarginDrawable(int resId) {
-        setPageMarginDrawable(getContext().getResources().getDrawable(resId));
-    }
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        return super.verifyDrawable(who) || who == mMarginDrawable;
-    }
-
-    @Override
-    protected void drawableStateChanged() {
-        super.drawableStateChanged();
-        final Drawable d = mMarginDrawable;
-        if (d != null && d.isStateful()) {
-            d.setState(getDrawableState());
-        }
-    }
-
-    // We want the duration of the page snap animation to be influenced by the distance that
-    // the screen has to travel, however, we don't want this duration to be effected in a
-    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
-    // of travel has on the overall snap duration.
-    float distanceInfluenceForSnapDuration(float f) {
-        f -= 0.5f; // center the values about 0.
-        f *= 0.3f * Math.PI / 2.0f;
-        return (float) Math.sin(f);
-    }
-
-    /**
-     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
-     *
-     * @param x the number of pixels to scroll by on the X axis
-     * @param y the number of pixels to scroll by on the Y axis
-     */
-    void smoothScrollTo(int x, int y) {
-        smoothScrollTo(x, y, 0);
-    }
-
-    /**
-     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
-     *
-     * @param x        the number of pixels to scroll by on the X axis
-     * @param y        the number of pixels to scroll by on the Y axis
-     * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
-     */
-    void smoothScrollTo(int x, int y, int velocity) {
-        if (getChildCount() == 0) {
-            // Nothing to do.
-            setScrollingCacheEnabled(false);
-            return;
-        }
-        int sx = getScrollX();
-        int sy = getScrollY();
-        int dx = x - sx;
-        int dy = y - sy;
-        if (dx == 0 && dy == 0) {
-            completeScroll(false);
-            populate();
-            setScrollState(SCROLL_STATE_IDLE);
-            return;
-        }
-
-        setScrollingCacheEnabled(true);
-        setScrollState(SCROLL_STATE_SETTLING);
-
-        final int height = getClientHeight();
-        final int halfHeight = height / 2;
-        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / height);
-        final float distance = halfHeight + halfHeight *
-                distanceInfluenceForSnapDuration(distanceRatio);
-
-        int duration = 0;
-        velocity = Math.abs(velocity);
-        if (velocity > 0) {
-            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
-        } else {
-            final float pageHeight = height * mAdapter.getPageWidth(mCurItem);
-            final float pageDelta = (float) Math.abs(dx) / (pageHeight + mPageMargin);
-            duration = (int) ((pageDelta + 1) * 100);
-        }
-        duration = Math.min(duration, MAX_SETTLE_DURATION);
-
-        mScroller.startScroll(sx, sy, dx, dy, duration);
-        ViewCompat.postInvalidateOnAnimation(this);
-    }
-
-    ItemInfo addNewItem(int position, int index) {
-        ItemInfo ii = new ItemInfo();
-        ii.position = position;
-        ii.object = mAdapter.instantiateItem(this, position);
-        ii.heightFactor = mAdapter.getPageWidth(position);
-        if (index < 0 || index >= mItems.size()) {
-            mItems.add(ii);
-        } else {
-            mItems.add(index, ii);
-        }
-        return ii;
-    }
-
-    void dataSetChanged() {
-        // This method only gets called if our observer is attached, so mAdapter is non-null.
-
-        final int adapterCount = mAdapter.getCount();
-        mExpectedAdapterCount = adapterCount;
-        boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
-                mItems.size() < adapterCount;
-        int newCurrItem = mCurItem;
-
-        boolean isUpdating = false;
-        for (int i = 0; i < mItems.size(); i++) {
-            final ItemInfo ii = mItems.get(i);
-            final int newPos = mAdapter.getItemPosition(ii.object);
-
-            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
-                continue;
-            }
-
-            if (newPos == PagerAdapter.POSITION_NONE) {
-                mItems.remove(i);
-                i--;
-
-                if (!isUpdating) {
-                    mAdapter.startUpdate(this);
-                    isUpdating = true;
-                }
-
-                mAdapter.destroyItem(this, ii.position, ii.object);
-                needPopulate = true;
-
-                if (mCurItem == ii.position) {
-                    // Keep the current item in the valid range
-                    newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
-                    needPopulate = true;
-                }
-                continue;
-            }
-
-            if (ii.position != newPos) {
-                if (ii.position == mCurItem) {
-                    // Our current item changed position. Follow it.
-                    newCurrItem = newPos;
-                }
-
-                ii.position = newPos;
-                needPopulate = true;
-            }
-        }
-
-        if (isUpdating) {
-            mAdapter.finishUpdate(this);
-        }
-
-        Collections.sort(mItems, COMPARATOR);
-
-        if (needPopulate) {
-            // Reset our known page widths; populate will recompute them.
-            final int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                if (!lp.isDecor) {
-                    lp.heightFactor = 0.f;
-                }
-            }
-
-            setCurrentItemInternal(newCurrItem, false, true);
-            requestLayout();
-        }
-    }
-
-    void populate() {
-        populate(mCurItem);
-    }
-
-    void populate(int newCurrentItem) {
-        ItemInfo oldCurInfo = null;
-        int focusDirection = View.FOCUS_FORWARD;
-        if (mCurItem != newCurrentItem) {
-            focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP;
-            oldCurInfo = infoForPosition(mCurItem);
-            mCurItem = newCurrentItem;
-        }
-
-        if (mAdapter == null) {
-            sortChildDrawingOrder();
-            return;
-        }
-
-        // Bail now if we are waiting to populate.  This is to hold off
-        // on creating views from the time the user releases their finger to
-        // fling to a new position until we have finished the scroll to
-        // that position, avoiding glitches from happening at that point.
-        if (mPopulatePending) {
-            if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
-            sortChildDrawingOrder();
-            return;
-        }
-
-        // Also, don't populate until we are attached to a window.  This is to
-        // avoid trying to populate before we have restored our view hierarchy
-        // state and conflicting with what is restored.
-        if (getWindowToken() == null) {
-            return;
-        }
-
-        mAdapter.startUpdate(this);
-
-        final int pageLimit = mOffscreenPageLimit;
-        final int startPos = Math.max(0, mCurItem - pageLimit);
-        final int N = mAdapter.getCount();
-        final int endPos = Math.min(N - 1, mCurItem + pageLimit);
-
-        if (N != mExpectedAdapterCount) {
-            String resName;
-            try {
-                resName = getResources().getResourceName(getId());
-            } catch (Resources.NotFoundException e) {
-                resName = Integer.toHexString(getId());
-            }
-            throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
-                    " contents without calling PagerAdapter#notifyDataSetChanged!" +
-                    " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
-                    " Pager id: " + resName +
-                    " Pager class: " + getClass() +
-                    " Problematic adapter: " + mAdapter.getClass());
-        }
-
-        // Locate the currently focused item or add it if needed.
-        int curIndex = -1;
-        ItemInfo curItem = null;
-        for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
-            final ItemInfo ii = mItems.get(curIndex);
-            if (ii.position >= mCurItem) {
-                if (ii.position == mCurItem) curItem = ii;
-                break;
-            }
-        }
-
-        if (curItem == null && N > 0) {
-            curItem = addNewItem(mCurItem, curIndex);
-        }
-
-        // Fill 3x the available width or up to the number of offscreen
-        // pages requested to either side, whichever is larger.
-        // If we have no current item we have no work to do.
-        if (curItem != null) {
-            float extraHeightTop = 0.f;
-            int itemIndex = curIndex - 1;
-            ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
-            final int clientHeight = getClientHeight();
-            final float topHeightNeeded = clientHeight <= 0 ? 0 :
-                    2.f - curItem.heightFactor + (float) getPaddingLeft() / (float) clientHeight;
-            for (int pos = mCurItem - 1; pos >= 0; pos--) {
-                if (extraHeightTop >= topHeightNeeded && pos < startPos) {
-                    if (ii == null) {
-                        break;
-                    }
-                    if (pos == ii.position && !ii.scrolling) {
-                        mItems.remove(itemIndex);
-                        mAdapter.destroyItem(this, pos, ii.object);
-                        if (DEBUG) {
-                            Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
-                                    " view: " + ((View) ii.object));
-                        }
-                        itemIndex--;
-                        curIndex--;
-                        ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
-                    }
-                } else if (ii != null && pos == ii.position) {
-                    extraHeightTop += ii.heightFactor;
-                    itemIndex--;
-                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
-                } else {
-                    ii = addNewItem(pos, itemIndex + 1);
-                    extraHeightTop += ii.heightFactor;
-                    curIndex++;
-                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
-                }
-            }
-
-            float extraHeightBottom = curItem.heightFactor;
-            itemIndex = curIndex + 1;
-            if (extraHeightBottom < 2.f) {
-                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
-                final float bottomHeightNeeded = clientHeight <= 0 ? 0 :
-                        (float) getPaddingRight() / (float) clientHeight + 2.f;
-                for (int pos = mCurItem + 1; pos < N; pos++) {
-                    if (extraHeightBottom >= bottomHeightNeeded && pos > endPos) {
-                        if (ii == null) {
-                            break;
-                        }
-                        if (pos == ii.position && !ii.scrolling) {
-                            mItems.remove(itemIndex);
-                            mAdapter.destroyItem(this, pos, ii.object);
-                            if (DEBUG) {
-                                Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
-                                        " view: " + ((View) ii.object));
-                            }
-                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
-                        }
-                    } else if (ii != null && pos == ii.position) {
-                        extraHeightBottom += ii.heightFactor;
-                        itemIndex++;
-                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
-                    } else {
-                        ii = addNewItem(pos, itemIndex);
-                        itemIndex++;
-                        extraHeightBottom += ii.heightFactor;
-                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
-                    }
-                }
-            }
-
-            calculatePageOffsets(curItem, curIndex, oldCurInfo);
-        }
-
-        if (DEBUG) {
-            Log.i(TAG, "Current page list:");
-            for (int i = 0; i < mItems.size(); i++) {
-                Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
-            }
-        }
-
-        mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
-
-        mAdapter.finishUpdate(this);
-
-        // Check width measurement of current pages and drawing sort order.
-        // Update LayoutParams as needed.
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            lp.childIndex = i;
-            if (!lp.isDecor && lp.heightFactor == 0.f) {
-                // 0 means requery the adapter for this, it doesn't have a valid width.
-                final ItemInfo ii = infoForChild(child);
-                if (ii != null) {
-                    lp.heightFactor = ii.heightFactor;
-                    lp.position = ii.position;
-                }
-            }
-        }
-        sortChildDrawingOrder();
-
-        if (hasFocus()) {
-            View currentFocused = findFocus();
-            ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
-            if (ii == null || ii.position != mCurItem) {
-                for (int i = 0; i < getChildCount(); i++) {
-                    View child = getChildAt(i);
-                    ii = infoForChild(child);
-                    if (ii != null && ii.position == mCurItem) {
-                        if (child.requestFocus(focusDirection)) {
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private void sortChildDrawingOrder() {
-        if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
-            if (mDrawingOrderedChildren == null) {
-                mDrawingOrderedChildren = new ArrayList<View>();
-            } else {
-                mDrawingOrderedChildren.clear();
-            }
-            final int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-                mDrawingOrderedChildren.add(child);
-            }
-            Collections.sort(mDrawingOrderedChildren, sPositionComparator);
-        }
-    }
-
-    private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
-        final int N = mAdapter.getCount();
-        final int height = getClientHeight();
-        final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
-        // Fix up offsets for later layout.
-        if (oldCurInfo != null) {
-            final int oldCurPosition = oldCurInfo.position;
-            // Base offsets off of oldCurInfo.
-            if (oldCurPosition < curItem.position) {
-                int itemIndex = 0;
-                ItemInfo ii = null;
-                float offset = oldCurInfo.offset + oldCurInfo.heightFactor + marginOffset;
-                for (int pos = oldCurPosition + 1;
-                     pos <= curItem.position && itemIndex < mItems.size(); pos++) {
-                    ii = mItems.get(itemIndex);
-                    while (pos > ii.position && itemIndex < mItems.size() - 1) {
-                        itemIndex++;
-                        ii = mItems.get(itemIndex);
-                    }
-                    while (pos < ii.position) {
-                        // We don't have an item populated for this,
-                        // ask the adapter for an offset.
-                        offset += mAdapter.getPageWidth(pos) + marginOffset;
-                        pos++;
-                    }
-                    ii.offset = offset;
-                    offset += ii.heightFactor + marginOffset;
-                }
-            } else if (oldCurPosition > curItem.position) {
-                int itemIndex = mItems.size() - 1;
-                ItemInfo ii = null;
-                float offset = oldCurInfo.offset;
-                for (int pos = oldCurPosition - 1;
-                     pos >= curItem.position && itemIndex >= 0; pos--) {
-                    ii = mItems.get(itemIndex);
-                    while (pos < ii.position && itemIndex > 0) {
-                        itemIndex--;
-                        ii = mItems.get(itemIndex);
-                    }
-                    while (pos > ii.position) {
-                        // We don't have an item populated for this,
-                        // ask the adapter for an offset.
-                        offset -= mAdapter.getPageWidth(pos) + marginOffset;
-                        pos--;
-                    }
-                    offset -= ii.heightFactor + marginOffset;
-                    ii.offset = offset;
-                }
-            }
-        }
-
-        // Base all offsets off of curItem.
-        final int itemCount = mItems.size();
-        float offset = curItem.offset;
-        int pos = curItem.position - 1;
-        mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
-        mLastOffset = curItem.position == N - 1 ?
-                curItem.offset + curItem.heightFactor - 1 : Float.MAX_VALUE;
-        // Previous pages
-        for (int i = curIndex - 1; i >= 0; i--, pos--) {
-            final ItemInfo ii = mItems.get(i);
-            while (pos > ii.position) {
-                offset -= mAdapter.getPageWidth(pos--) + marginOffset;
-            }
-            offset -= ii.heightFactor + marginOffset;
-            ii.offset = offset;
-            if (ii.position == 0) mFirstOffset = offset;
-        }
-        offset = curItem.offset + curItem.heightFactor + marginOffset;
-        pos = curItem.position + 1;
-        // Next pages
-        for (int i = curIndex + 1; i < itemCount; i++, pos++) {
-            final ItemInfo ii = mItems.get(i);
-            while (pos < ii.position) {
-                offset += mAdapter.getPageWidth(pos++) + marginOffset;
-            }
-            if (ii.position == N - 1) {
-                mLastOffset = offset + ii.heightFactor - 1;
-            }
-            ii.offset = offset;
-            offset += ii.heightFactor + marginOffset;
-        }
-
-        mNeedCalculatePageOffsets = false;
-    }
-
-    /**
-     * This is the persistent state that is saved by ViewPager.  Only needed
-     * if you are creating a sublass of ViewPager that must save its own
-     * state, in which case it should implement a subclass of this which
-     * contains that state.
-     */
-    public static class SavedState extends BaseSavedState {
-        int position;
-        Parcelable adapterState;
-        ClassLoader loader;
-
-        public SavedState(Parcelable superState) {
-            super(superState);
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            super.writeToParcel(out, flags);
-            out.writeInt(position);
-            out.writeParcelable(adapterState, flags);
-        }
-
-        @Override
-        public String toString() {
-            return "FragmentPager.SavedState{"
-                    + Integer.toHexString(System.identityHashCode(this))
-                    + " position=" + position + "}";
-        }
-
-        public static final Creator<SavedState> CREATOR
-                = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
-            @Override
-            public SavedState createFromParcel(Parcel in, ClassLoader loader) {
-                return new SavedState(in, loader);
-            }
-
-            @Override
-            public SavedState[] newArray(int size) {
-                return new SavedState[size];
-            }
-        });
-
-        SavedState(Parcel in, ClassLoader loader) {
-            super(in);
-            if (loader == null) {
-                loader = getClass().getClassLoader();
-            }
-            position = in.readInt();
-            adapterState = in.readParcelable(loader);
-            this.loader = loader;
-        }
-    }
-
-    @Override
-    public Parcelable onSaveInstanceState() {
-        Parcelable superState = super.onSaveInstanceState();
-        SavedState ss = new SavedState(superState);
-        ss.position = mCurItem;
-        if (mAdapter != null) {
-            ss.adapterState = mAdapter.saveState();
-        }
-        return ss;
-    }
-
-    @Override
-    public void onRestoreInstanceState(Parcelable state) {
-        if (!(state instanceof SavedState)) {
-            super.onRestoreInstanceState(state);
-            return;
-        }
-
-        SavedState ss = (SavedState) state;
-        super.onRestoreInstanceState(ss.getSuperState());
-
-        if (mAdapter != null) {
-            mAdapter.restoreState(ss.adapterState, ss.loader);
-            setCurrentItemInternal(ss.position, false, true);
-        } else {
-            mRestoredCurItem = ss.position;
-            mRestoredAdapterState = ss.adapterState;
-            mRestoredClassLoader = ss.loader;
-        }
-    }
-
-    @Override
-    public void addView(View child, int index, ViewGroup.LayoutParams params) {
-        if (!checkLayoutParams(params)) {
-            params = generateLayoutParams(params);
-        }
-        final LayoutParams lp = (LayoutParams) params;
-        lp.isDecor |= child instanceof Decor;
-        if (mInLayout) {
-            if (lp != null && lp.isDecor) {
-                throw new IllegalStateException("Cannot add pager decor view during layout");
-            }
-            lp.needsMeasure = true;
-            addViewInLayout(child, index, params);
-        } else {
-            super.addView(child, index, params);
-        }
-
-        if (USE_CACHE) {
-            if (child.getVisibility() != GONE) {
-                child.setDrawingCacheEnabled(mScrollingCacheEnabled);
-            } else {
-                child.setDrawingCacheEnabled(false);
-            }
-        }
-    }
-
-    @Override
-    public void removeView(View view) {
-        if (mInLayout) {
-            removeViewInLayout(view);
-        } else {
-            super.removeView(view);
-        }
-    }
-
-    ItemInfo infoForChild(View child) {
-        for (int i = 0; i < mItems.size(); i++) {
-            ItemInfo ii = mItems.get(i);
-            if (mAdapter.isViewFromObject(child, ii.object)) {
-                return ii;
-            }
-        }
-        return null;
-    }
-
-    ItemInfo infoForAnyChild(View child) {
-        ViewParent parent;
-        while ((parent = child.getParent()) != this) {
-            if (parent == null || !(parent instanceof View)) {
-                return null;
-            }
-            child = (View) parent;
-        }
-        return infoForChild(child);
-    }
-
-    ItemInfo infoForPosition(int position) {
-        for (int i = 0; i < mItems.size(); i++) {
-            ItemInfo ii = mItems.get(i);
-            if (ii.position == position) {
-                return ii;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mFirstLayout = true;
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // For simple implementation, our internal size is always 0.
-        // We depend on the container to specify the layout size of
-        // our view.  We can't really know what it is since we will be
-        // adding and removing different arbitrary views and do not
-        // want the layout to change as this happens.
-        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
-                getDefaultSize(0, heightMeasureSpec));
-
-        final int measuredHeight = getMeasuredHeight();
-        final int maxGutterSize = measuredHeight / 10;
-        mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
-
-        // Children are just made to fill our space.
-        int childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
-        int childHeightSize = measuredHeight - getPaddingTop() - getPaddingBottom();
-
-        /*
-         * Make sure all children have been properly measured. Decor views first.
-         * Right now we cheat and make this less complicated by assuming decor
-         * views won't intersect. We will pin to edges based on gravity.
-         */
-        int size = getChildCount();
-        for (int i = 0; i < size; ++i) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() != GONE) {
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                if (lp != null && lp.isDecor) {
-                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
-                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
-                    int widthMode = MeasureSpec.AT_MOST;
-                    int heightMode = MeasureSpec.AT_MOST;
-                    boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
-                    boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
-
-                    if (consumeVertical) {
-                        widthMode = MeasureSpec.EXACTLY;
-                    } else if (consumeHorizontal) {
-                        heightMode = MeasureSpec.EXACTLY;
-                    }
-
-                    int widthSize = childWidthSize;
-                    int heightSize = childHeightSize;
-                    if (lp.width != LayoutParams.WRAP_CONTENT) {
-                        widthMode = MeasureSpec.EXACTLY;
-                        if (lp.width != LayoutParams.FILL_PARENT) {
-                            widthSize = lp.width;
-                        }
-                    }
-                    if (lp.height != LayoutParams.WRAP_CONTENT) {
-                        heightMode = MeasureSpec.EXACTLY;
-                        if (lp.height != LayoutParams.FILL_PARENT) {
-                            heightSize = lp.height;
-                        }
-                    }
-                    final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
-                    final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
-                    child.measure(widthSpec, heightSpec);
-
-                    if (consumeVertical) {
-                        childHeightSize -= child.getMeasuredHeight();
-                    } else if (consumeHorizontal) {
-                        childWidthSize -= child.getMeasuredWidth();
-                    }
-                }
-            }
-        }
-
-        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
-        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
-
-        // Make sure we have created all fragments that we need to have shown.
-        mInLayout = true;
-        populate();
-        mInLayout = false;
-
-        // Page views next.
-        size = getChildCount();
-        for (int i = 0; i < size; ++i) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() != GONE) {
-                if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
-                        + ": " + mChildWidthMeasureSpec);
-
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                if (lp != null || !lp.isDecor) {
-                    final int heightSpec = MeasureSpec.makeMeasureSpec(
-                            (int) (childHeightSize * lp.heightFactor), MeasureSpec.EXACTLY);
-                    child.measure(mChildWidthMeasureSpec, heightSpec);
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-
-        // Make sure scroll position is set correctly.
-        if (h != oldh) {
-            recomputeScrollPosition(h, oldh, mPageMargin, mPageMargin);
-        }
-    }
-
-    private void recomputeScrollPosition(int height, int oldHeight, int margin, int oldMargin) {
-        if (oldHeight > 0 && !mItems.isEmpty()) {
-            final int heightWithMargin = height - getPaddingTop() - getPaddingBottom() + margin;
-            final int oldHeightWithMargin = oldHeight - getPaddingTop() - getPaddingBottom()
-                    + oldMargin;
-            final int ypos = getScrollY();
-            final float pageOffset = (float) ypos / oldHeightWithMargin;
-            final int newOffsetPixels = (int) (pageOffset * heightWithMargin);
-
-            scrollTo(getScrollX(), newOffsetPixels);
-            if (!mScroller.isFinished()) {
-                // We now return to your regularly scheduled scroll, already in progress.
-                final int newDuration = mScroller.getDuration() - mScroller.timePassed();
-                ItemInfo targetInfo = infoForPosition(mCurItem);
-                mScroller.startScroll(0, newOffsetPixels,
-                        0, (int) (targetInfo.offset * height), newDuration);
-            }
-        } else {
-            final ItemInfo ii = infoForPosition(mCurItem);
-            final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
-            final int scrollPos = (int) (scrollOffset *
-                    (height - getPaddingTop() - getPaddingBottom()));
-            if (scrollPos != getScrollY()) {
-                completeScroll(false);
-                scrollTo(getScrollX(), scrollPos);
-            }
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        final int count = getChildCount();
-        int width = r - l;
-        int height = b - t;
-        int paddingLeft = getPaddingLeft();
-        int paddingTop = getPaddingTop();
-        int paddingRight = getPaddingRight();
-        int paddingBottom = getPaddingBottom();
-        final int scrollY = getScrollY();
-
-        int decorCount = 0;
-
-        // First pass - decor views. We need to do this in two passes so that
-        // we have the proper offsets for non-decor views later.
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() != GONE) {
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                int childLeft = 0;
-                int childTop = 0;
-                if (lp.isDecor) {
-                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
-                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
-                    switch (hgrav) {
-                        default:
-                            childLeft = paddingLeft;
-                            break;
-                        case Gravity.LEFT:
-                            childLeft = paddingLeft;
-                            paddingLeft += child.getMeasuredWidth();
-                            break;
-                        case Gravity.CENTER_HORIZONTAL:
-                            childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
-                                    paddingLeft);
-                            break;
-                        case Gravity.RIGHT:
-                            childLeft = width - paddingRight - child.getMeasuredWidth();
-                            paddingRight += child.getMeasuredWidth();
-                            break;
-                    }
-                    switch (vgrav) {
-                        default:
-                            childTop = paddingTop;
-                            break;
-                        case Gravity.TOP:
-                            childTop = paddingTop;
-                            paddingTop += child.getMeasuredHeight();
-                            break;
-                        case Gravity.CENTER_VERTICAL:
-                            childTop = Math.max((height - child.getMeasuredHeight()) / 2,
-                                    paddingTop);
-                            break;
-                        case Gravity.BOTTOM:
-                            childTop = height - paddingBottom - child.getMeasuredHeight();
-                            paddingBottom += child.getMeasuredHeight();
-                            break;
-                    }
-                    childTop += scrollY;
-                    child.layout(childLeft, childTop,
-                            childLeft + child.getMeasuredWidth(),
-                            childTop + child.getMeasuredHeight());
-                    decorCount++;
-                }
-            }
-        }
-
-        final int childHeight = height - paddingTop - paddingBottom;
-        // Page views. Do this once we have the right padding offsets from above.
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() != GONE) {
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                ItemInfo ii;
-                if (!lp.isDecor && (ii = infoForChild(child)) != null) {
-                    int toff = (int) (childHeight * ii.offset);
-                    int childLeft = paddingLeft;
-                    int childTop = paddingTop + toff;
-                    if (lp.needsMeasure) {
-                        // This was added during layout and needs measurement.
-                        // Do it now that we know what we're working with.
-                        lp.needsMeasure = false;
-                        final int widthSpec = MeasureSpec.makeMeasureSpec(
-                                (int) (width - paddingLeft - paddingRight),
-                                MeasureSpec.EXACTLY);
-                        final int heightSpec = MeasureSpec.makeMeasureSpec(
-                                (int) (childHeight * lp.heightFactor),
-                                MeasureSpec.EXACTLY);
-                        child.measure(widthSpec, heightSpec);
-                    }
-                    if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
-                            + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
-                            + "x" + child.getMeasuredHeight());
-                    child.layout(childLeft, childTop,
-                            childLeft + child.getMeasuredWidth(),
-                            childTop + child.getMeasuredHeight());
-                }
-            }
-        }
-        mLeftPageBounds = paddingLeft;
-        mRightPageBounds = width - paddingRight;
-        mDecorChildCount = decorCount;
-
-        if (mFirstLayout) {
-            scrollToItem(mCurItem, false, 0, false);
-        }
-        mFirstLayout = false;
-    }
-
-    @Override
-    public void computeScroll() {
-        if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
-            int oldX = getScrollX();
-            int oldY = getScrollY();
-            int x = mScroller.getCurrX();
-            int y = mScroller.getCurrY();
-
-            if (oldX != x || oldY != y) {
-                scrollTo(x, y);
-                if (!pageScrolled(y)) {
-                    mScroller.abortAnimation();
-                    scrollTo(x, 0);
-                }
-            }
-
-            // Keep on drawing until the animation has finished.
-            ViewCompat.postInvalidateOnAnimation(this);
-            return;
-        }
-
-        // Done with scroll, clean up state.
-        completeScroll(true);
-    }
-
-    private boolean pageScrolled(int ypos) {
-        if (mItems.size() == 0) {
-            mCalledSuper = false;
-            onPageScrolled(0, 0, 0);
-            if (!mCalledSuper) {
-                throw new IllegalStateException(
-                        "onPageScrolled did not call superclass implementation");
-            }
-            return false;
-        }
-        final ItemInfo ii = infoForCurrentScrollPosition();
-        final int height = getClientHeight();
-        final int heightWithMargin = height + mPageMargin;
-        final float marginOffset = (float) mPageMargin / height;
-        final int currentPage = ii.position;
-        final float pageOffset = (((float) ypos / height) - ii.offset) /
-                (ii.heightFactor + marginOffset);
-        final int offsetPixels = (int) (pageOffset * heightWithMargin);
-
-        mCalledSuper = false;
-        onPageScrolled(currentPage, pageOffset, offsetPixels);
-        if (!mCalledSuper) {
-            throw new IllegalStateException(
-                    "onPageScrolled did not call superclass implementation");
-        }
-        return true;
-    }
-
-    /**
-     * This method will be invoked when the current page is scrolled, either as part
-     * of a programmatically initiated smooth scroll or a user initiated touch scroll.
-     * If you override this method you must call through to the superclass implementation
-     * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
-     * returns.
-     *
-     * @param position     Position index of the first page currently being displayed.
-     *                     Page position+1 will be visible if positionOffset is nonzero.
-     * @param offset       Value from [0, 1) indicating the offset from the page at position.
-     * @param offsetPixels Value in pixels indicating the offset from position.
-     */
-    protected void onPageScrolled(int position, float offset, int offsetPixels) {
-        // Offset any decor views if needed - keep them on-screen at all times.
-        if (mDecorChildCount > 0) {
-            final int scrollY = getScrollY();
-            int paddingTop = getPaddingTop();
-            int paddingBottom = getPaddingBottom();
-            final int height = getHeight();
-            final int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                if (!lp.isDecor) continue;
-
-                final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
-                int childTop = 0;
-                switch (vgrav) {
-                    default:
-                        childTop = paddingTop;
-                        break;
-                    case Gravity.TOP:
-                        childTop = paddingTop;
-                        paddingTop += child.getHeight();
-                        break;
-                    case Gravity.CENTER_VERTICAL:
-                        childTop = Math.max((height - child.getMeasuredHeight()) / 2,
-                                paddingTop);
-                        break;
-                    case Gravity.BOTTOM:
-                        childTop = height - paddingBottom - child.getMeasuredHeight();
-                        paddingBottom += child.getMeasuredHeight();
-                        break;
-                }
-                childTop += scrollY;
-
-                final int childOffset = childTop - child.getTop();
-                if (childOffset != 0) {
-                    child.offsetTopAndBottom(childOffset);
-                }
-            }
-        }
-
-        if (mOnPageChangeListener != null) {
-            mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
-        }
-        if (mInternalPageChangeListener != null) {
-            mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
-        }
-
-        if (mPageTransformer != null) {
-            final int scrollY = getScrollY();
-            final int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-                if (lp.isDecor) continue;
-
-                final float transformPos = (float) (child.getTop() - scrollY) / getClientHeight();
-                mPageTransformer.transformPage(child, transformPos);
-            }
-        }
-
-        mCalledSuper = true;
-    }
-
-    private void completeScroll(boolean postEvents) {
-        boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
-        if (needPopulate) {
-            // Done with scroll, no longer want to cache view drawing.
-            setScrollingCacheEnabled(false);
-            mScroller.abortAnimation();
-            int oldX = getScrollX();
-            int oldY = getScrollY();
-            int x = mScroller.getCurrX();
-            int y = mScroller.getCurrY();
-            if (oldX != x || oldY != y) {
-                scrollTo(x, y);
-            }
-        }
-        mPopulatePending = false;
-        for (int i = 0; i < mItems.size(); i++) {
-            ItemInfo ii = mItems.get(i);
-            if (ii.scrolling) {
-                needPopulate = true;
-                ii.scrolling = false;
-            }
-        }
-        if (needPopulate) {
-            if (postEvents) {
-                ViewCompat.postOnAnimation(this, mEndScrollRunnable);
-            } else {
-                mEndScrollRunnable.run();
-            }
-        }
-    }
-
-    private boolean isGutterDrag(float y, float dy) {
-        return (y < mGutterSize && dy > 0) || (y > getHeight() - mGutterSize && dy < 0);
-    }
-
-    private void enableLayers(boolean enable) {
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final int layerType = enable ?
-                    ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
-            ViewCompat.setLayerType(getChildAt(i), layerType, null);
-        }
-    }
-
-    @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.
-         */
-
-        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
-
-        // Always take care of the touch gesture being complete.
-        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
-            // Release the drag.
-            if (DEBUG) Log.v(TAG, "Intercept done!");
-            mIsBeingDragged = false;
-            mIsUnableToDrag = false;
-            mActivePointerId = INVALID_POINTER;
-            if (mVelocityTracker != null) {
-                mVelocityTracker.recycle();
-                mVelocityTracker = null;
-            }
-            return false;
-        }
-
-        // Nothing more to do here if we have decided whether or not we
-        // are dragging.
-        if (action != MotionEvent.ACTION_DOWN) {
-            if (mIsBeingDragged) {
-                if (DEBUG) Log.v(TAG, "Intercept returning true!");
-                return true;
-            }
-            if (mIsUnableToDrag) {
-                if (DEBUG) Log.v(TAG, "Intercept returning false!");
-                return false;
-            }
-        }
-
-        switch (action) {
-            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.
-                    break;
-                }
-
-                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
-                final float y = MotionEventCompat.getY(ev, pointerIndex);
-                final float dy = y - mLastMotionY;
-                final float yDiff = Math.abs(dy);
-                final float x = MotionEventCompat.getX(ev, pointerIndex);
-                final float xDiff = Math.abs(x - mInitialMotionX);
-                if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
-
-                if (dy != 0 && !isGutterDrag(mLastMotionY, dy) &&
-                        canScroll(this, false, (int) dy, (int) x, (int) y)) {
-                    // Nested view has scrollable area under this point. Let it be handled there.
-                    mLastMotionX = x;
-                    mLastMotionY = y;
-                    mIsUnableToDrag = true;
-                    return false;
-                }
-                if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) {
-                    if (DEBUG) Log.v(TAG, "Starting drag!");
-                    mIsBeingDragged = true;
-                    requestParentDisallowInterceptTouchEvent(true);
-                    setScrollState(SCROLL_STATE_DRAGGING);
-                    mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop :
-                            mInitialMotionY - mTouchSlop;
-                    mLastMotionX = x;
-                    setScrollingCacheEnabled(true);
-                } else if (xDiff > mTouchSlop) {
-                    // The finger has moved enough in the vertical
-                    // direction to be counted as a drag...  abort
-                    // any attempt to drag horizontally, to work correctly
-                    // with children that have scrolling containers.
-                    if (DEBUG) Log.v(TAG, "Starting unable to drag!");
-                    mIsUnableToDrag = true;
-                }
-                if (mIsBeingDragged) {
-                    // Scroll to follow the motion event
-                    if (performDrag(y)) {
-                        ViewCompat.postInvalidateOnAnimation(this);
-                    }
-                }
-                break;
-            }
-
-            case MotionEvent.ACTION_DOWN: {
-                /*
-                 * Remember location of down touch.
-                 * ACTION_DOWN always refers to pointer index 0.
-                 */
-                mLastMotionX = mInitialMotionX = ev.getX();
-                mLastMotionY = mInitialMotionY = ev.getY();
-                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
-                mIsUnableToDrag = false;
-
-                mScroller.computeScrollOffset();
-                if (mScrollState == SCROLL_STATE_SETTLING &&
-                        Math.abs(mScroller.getFinalY() - mScroller.getCurrY()) > mCloseEnough) {
-                    // Let the user 'catch' the pager as it animates.
-                    mScroller.abortAnimation();
-                    mPopulatePending = false;
-                    populate();
-                    mIsBeingDragged = true;
-                    requestParentDisallowInterceptTouchEvent(true);
-                    setScrollState(SCROLL_STATE_DRAGGING);
-                } else {
-                    completeScroll(false);
-                    mIsBeingDragged = false;
-                }
-
-                if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
-                        + " mIsBeingDragged=" + mIsBeingDragged
-                        + "mIsUnableToDrag=" + mIsUnableToDrag);
-                break;
-            }
-
-            case MotionEventCompat.ACTION_POINTER_UP:
-                onSecondaryPointerUp(ev);
-                break;
-        }
-
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        mVelocityTracker.addMovement(ev);
-
-        /*
-         * The only time we want to intercept motion events is if we are in the
-         * drag mode.
-         */
-        return mIsBeingDragged;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (mFakeDragging) {
-            // A fake drag is in progress already, ignore this real one
-            // but still eat the touch events.
-            // (It is likely that the user is multi-touching the screen.)
-            return true;
-        }
-
-        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
-            // Don't handle edge touches immediately -- they may actually belong to one of our
-            // descendants.
-            return false;
-        }
-
-        if (mAdapter == null || mAdapter.getCount() == 0) {
-            // Nothing to present or scroll; nothing to touch.
-            return false;
-        }
-
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        mVelocityTracker.addMovement(ev);
-
-        final int action = ev.getAction();
-        boolean needsInvalidate = false;
-
-        switch (action & MotionEventCompat.ACTION_MASK) {
-            case MotionEvent.ACTION_DOWN: {
-                mScroller.abortAnimation();
-                mPopulatePending = false;
-                populate();
-
-                // Remember where the motion event started
-                mLastMotionX = mInitialMotionX = ev.getX();
-                mLastMotionY = mInitialMotionY = ev.getY();
-                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
-                break;
-            }
-            case MotionEvent.ACTION_MOVE:
-                if (!mIsBeingDragged) {
-                    final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
-                    final float y = MotionEventCompat.getY(ev, pointerIndex);
-                    final float yDiff = Math.abs(y - mLastMotionY);
-                    final float x = MotionEventCompat.getX(ev, pointerIndex);
-                    final float xDiff = Math.abs(x - mLastMotionX);
-                    if (DEBUG)
-                        Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
-                    if (yDiff > mTouchSlop && yDiff > xDiff) {
-                        if (DEBUG) Log.v(TAG, "Starting drag!");
-                        mIsBeingDragged = true;
-                        requestParentDisallowInterceptTouchEvent(true);
-                        mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop :
-                                mInitialMotionY - mTouchSlop;
-                        mLastMotionX = x;
-                        setScrollState(SCROLL_STATE_DRAGGING);
-                        setScrollingCacheEnabled(true);
-
-                        // Disallow Parent Intercept, just in case
-                        ViewParent parent = getParent();
-                        if (parent != null) {
-                            parent.requestDisallowInterceptTouchEvent(true);
-                        }
-                    }
-                }
-                // Not else! Note that mIsBeingDragged can be set above.
-                if (mIsBeingDragged) {
-                    // Scroll to follow the motion event
-                    final int activePointerIndex = MotionEventCompat.findPointerIndex(
-                            ev, mActivePointerId);
-                    final float y = MotionEventCompat.getY(ev, activePointerIndex);
-                    needsInvalidate |= performDrag(y);
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-                if (mIsBeingDragged) {
-                    final VelocityTracker velocityTracker = mVelocityTracker;
-                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-                    int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
-                            velocityTracker, mActivePointerId);
-                    mPopulatePending = true;
-                    final int height = getClientHeight();
-                    final int scrollY = getScrollY();
-                    final ItemInfo ii = infoForCurrentScrollPosition();
-                    final int currentPage = ii.position;
-                    final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
-                    final int activePointerIndex =
-                            MotionEventCompat.findPointerIndex(ev, mActivePointerId);
-                    final float y = MotionEventCompat.getY(ev, activePointerIndex);
-                    final int totalDelta = (int) (y - mInitialMotionY);
-                    int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
-                            totalDelta);
-                    setCurrentItemInternal(nextPage, true, true, initialVelocity);
-
-                    mActivePointerId = INVALID_POINTER;
-                    endDrag();
-                    needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
-                }
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                if (mIsBeingDragged) {
-                    scrollToItem(mCurItem, true, 0, false);
-                    mActivePointerId = INVALID_POINTER;
-                    endDrag();
-                    needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
-                }
-                break;
-            case MotionEventCompat.ACTION_POINTER_DOWN: {
-                final int index = MotionEventCompat.getActionIndex(ev);
-                final float y = MotionEventCompat.getY(ev, index);
-                mLastMotionY = y;
-                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
-                break;
-            }
-            case MotionEventCompat.ACTION_POINTER_UP:
-                onSecondaryPointerUp(ev);
-                mLastMotionY = MotionEventCompat.getY(ev,
-                        MotionEventCompat.findPointerIndex(ev, mActivePointerId));
-                break;
-        }
-        if (needsInvalidate) {
-            ViewCompat.postInvalidateOnAnimation(this);
-        }
-        return true;
-    }
-
-    private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
-        final ViewParent parent = getParent();
-        if (parent != null) {
-            parent.requestDisallowInterceptTouchEvent(disallowIntercept);
-        }
-    }
-
-    private boolean performDrag(float y) {
-        boolean needsInvalidate = false;
-
-        final float deltaY = mLastMotionY - y;
-        mLastMotionY = y;
-
-        float oldScrollY = getScrollY();
-        float scrollY = oldScrollY + deltaY;
-        final int height = getClientHeight();
-
-        float topBound = height * mFirstOffset;
-        float bottomBound = height * mLastOffset;
-        boolean topAbsolute = true;
-        boolean bottomAbsolute = true;
-
-        final ItemInfo firstItem = mItems.get(0);
-        final ItemInfo lastItem = mItems.get(mItems.size() - 1);
-        if (firstItem.position != 0) {
-            topAbsolute = false;
-            topBound = firstItem.offset * height;
-        }
-        if (lastItem.position != mAdapter.getCount() - 1) {
-            bottomAbsolute = false;
-            bottomBound = lastItem.offset * height;
-        }
-
-        if (scrollY < topBound) {
-            if (topAbsolute) {
-                float over = topBound - scrollY;
-                needsInvalidate = mTopEdge.onPull(Math.abs(over) / height);
-            }
-            scrollY = topBound;
-        } else if (scrollY > bottomBound) {
-            if (bottomAbsolute) {
-                float over = scrollY - bottomBound;
-                needsInvalidate = mBottomEdge.onPull(Math.abs(over) / height);
-            }
-            scrollY = bottomBound;
-        }
-        // Don't lose the rounded component
-        mLastMotionX += scrollY - (int) scrollY;
-        scrollTo(getScrollX(), (int) scrollY);
-        pageScrolled((int) scrollY);
-
-        return needsInvalidate;
-    }
-
-    /**
-     * @return Info about the page at the current scroll position.
-     * This can be synthetic for a missing middle page; the 'object' field can be null.
-     */
-    private ItemInfo infoForCurrentScrollPosition() {
-        final int height = getClientHeight();
-        final float scrollOffset = height > 0 ? (float) getScrollY() / height : 0;
-        final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
-        int lastPos = -1;
-        float lastOffset = 0.f;
-        float lastHeight = 0.f;
-        boolean first = true;
-
-        ItemInfo lastItem = null;
-        for (int i = 0; i < mItems.size(); i++) {
-            ItemInfo ii = mItems.get(i);
-            float offset;
-            if (!first && ii.position != lastPos + 1) {
-                // Create a synthetic item for a missing page.
-                ii = mTempItem;
-                ii.offset = lastOffset + lastHeight + marginOffset;
-                ii.position = lastPos + 1;
-                ii.heightFactor = mAdapter.getPageWidth(ii.position);
-                i--;
-            }
-            offset = ii.offset;
-
-            final float topBound = offset;
-            final float bottomBound = offset + ii.heightFactor + marginOffset;
-            if (first || scrollOffset >= topBound) {
-                if (scrollOffset < bottomBound || i == mItems.size() - 1) {
-                    return ii;
-                }
-            } else {
-                return lastItem;
-            }
-            first = false;
-            lastPos = ii.position;
-            lastOffset = offset;
-            lastHeight = ii.heightFactor;
-            lastItem = ii;
-        }
-
-        return lastItem;
-    }
-
-    private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaY) {
-        int targetPage;
-        if (Math.abs(deltaY) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
-            targetPage = velocity > 0 ? currentPage : currentPage + 1;
-        } else {
-            final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
-            targetPage = (int) (currentPage + pageOffset + truncator);
-        }
-
-        if (mItems.size() > 0) {
-            final ItemInfo firstItem = mItems.get(0);
-            final ItemInfo lastItem = mItems.get(mItems.size() - 1);
-
-            // Only let the user target pages we have items for
-            targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
-        }
-
-        return targetPage;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        super.draw(canvas);
-        boolean needsInvalidate = false;
-
-        final int overScrollMode = ViewCompat.getOverScrollMode(this);
-        if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
-                (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
-                        mAdapter != null && mAdapter.getCount() > 1)) {
-            if (!mTopEdge.isFinished()) {
-                final int restoreCount = canvas.save();
-                final int height = getHeight();
-                final int width = getWidth() - getPaddingLeft() - getPaddingRight();
-
-                canvas.translate(getPaddingLeft(), mFirstOffset * height);
-                mTopEdge.setSize(width, height);
-                needsInvalidate |= mTopEdge.draw(canvas);
-                canvas.restoreToCount(restoreCount);
-            }
-            if (!mBottomEdge.isFinished()) {
-                final int restoreCount = canvas.save();
-                final int height = getHeight();
-                final int width = getWidth() - getPaddingLeft() - getPaddingRight();
-
-                canvas.rotate(180);
-                canvas.translate(-width - getPaddingLeft(), -(mLastOffset + 1) * height);
-                mBottomEdge.setSize(width, height);
-                needsInvalidate |= mBottomEdge.draw(canvas);
-                canvas.restoreToCount(restoreCount);
-            }
-        } else {
-            mTopEdge.finish();
-            mBottomEdge.finish();
-        }
-
-        if (needsInvalidate) {
-            // Keep animating
-            ViewCompat.postInvalidateOnAnimation(this);
-        }
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        // Draw the margin drawable between pages if needed.
-        if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
-            final int scrollY = getScrollY();
-            final int height = getHeight();
-
-            final float marginOffset = (float) mPageMargin / height;
-            int itemIndex = 0;
-            ItemInfo ii = mItems.get(0);
-            float offset = ii.offset;
-            final int itemCount = mItems.size();
-            final int firstPos = ii.position;
-            final int lastPos = mItems.get(itemCount - 1).position;
-            for (int pos = firstPos; pos < lastPos; pos++) {
-                while (pos > ii.position && itemIndex < itemCount) {
-                    ii = mItems.get(++itemIndex);
-                }
-
-                float drawAt;
-                if (pos == ii.position) {
-                    drawAt = (ii.offset + ii.heightFactor) * height;
-                    offset = ii.offset + ii.heightFactor + marginOffset;
-                } else {
-                    float heightFactor = mAdapter.getPageWidth(pos);
-                    drawAt = (offset + heightFactor) * height;
-                    offset += heightFactor + marginOffset;
-                }
-
-                if (drawAt + mPageMargin > scrollY) {
-                    mMarginDrawable.setBounds(mLeftPageBounds, (int) drawAt,
-                            mRightPageBounds, (int) (drawAt + mPageMargin + 0.5f));
-                    mMarginDrawable.draw(canvas);
-                }
-
-                if (drawAt > scrollY + height) {
-                    break; // No more visible, no sense in continuing
-                }
-            }
-        }
-    }
-
-    /**
-     * Start a fake drag of the pager.
-     * <p/>
-     * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
-     * with the touch scrolling of another view, while still letting the ViewPager
-     * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
-     * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
-     * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
-     * <p/>
-     * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
-     * is already in progress, this method will return false.
-     *
-     * @return true if the fake drag began successfully, false if it could not be started.
-     * @see #fakeDragBy(float)
-     * @see #endFakeDrag()
-     */
-    public boolean beginFakeDrag() {
-        if (mIsBeingDragged) {
-            return false;
-        }
-        mFakeDragging = true;
-        setScrollState(SCROLL_STATE_DRAGGING);
-        mInitialMotionY = mLastMotionY = 0;
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        } else {
-            mVelocityTracker.clear();
-        }
-        final long time = SystemClock.uptimeMillis();
-        final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
-        mVelocityTracker.addMovement(ev);
-        ev.recycle();
-        mFakeDragBeginTime = time;
-        return true;
-    }
-
-    /**
-     * End a fake drag of the pager.
-     *
-     * @see #beginFakeDrag()
-     * @see #fakeDragBy(float)
-     */
-    public void endFakeDrag() {
-        if (!mFakeDragging) {
-            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
-        }
-
-        final VelocityTracker velocityTracker = mVelocityTracker;
-        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-        int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
-                velocityTracker, mActivePointerId);
-        mPopulatePending = true;
-        final int height = getClientHeight();
-        final int scrollY = getScrollY();
-        final ItemInfo ii = infoForCurrentScrollPosition();
-        final int currentPage = ii.position;
-        final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
-        final int totalDelta = (int) (mLastMotionY - mInitialMotionY);
-        int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
-                totalDelta);
-        setCurrentItemInternal(nextPage, true, true, initialVelocity);
-        endDrag();
-
-        mFakeDragging = false;
-    }
-
-    /**
-     * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
-     *
-     * @param yOffset Offset in pixels to drag by.
-     * @see #beginFakeDrag()
-     * @see #endFakeDrag()
-     */
-    public void fakeDragBy(float yOffset) {
-        if (!mFakeDragging) {
-            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
-        }
-
-        mLastMotionY += yOffset;
-
-        float oldScrollY = getScrollY();
-        float scrollY = oldScrollY - yOffset;
-        final int height = getClientHeight();
-
-        float topBound = height * mFirstOffset;
-        float bottomBound = height * mLastOffset;
-
-        final ItemInfo firstItem = mItems.get(0);
-        final ItemInfo lastItem = mItems.get(mItems.size() - 1);
-        if (firstItem.position != 0) {
-            topBound = firstItem.offset * height;
-        }
-        if (lastItem.position != mAdapter.getCount() - 1) {
-            bottomBound = lastItem.offset * height;
-        }
-
-        if (scrollY < topBound) {
-            scrollY = topBound;
-        } else if (scrollY > bottomBound) {
-            scrollY = bottomBound;
-        }
-        // Don't lose the rounded component
-        mLastMotionY += scrollY - (int) scrollY;
-        scrollTo(getScrollX(), (int) scrollY);
-        pageScrolled((int) scrollY);
-
-        // Synthesize an event for the VelocityTracker.
-        final long time = SystemClock.uptimeMillis();
-        final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
-                0, mLastMotionY, 0);
-        mVelocityTracker.addMovement(ev);
-        ev.recycle();
-    }
-
-    /**
-     * Returns true if a fake drag is in progress.
-     *
-     * @return true if currently in a fake drag, false otherwise.
-     * @see #beginFakeDrag()
-     * @see #fakeDragBy(float)
-     * @see #endFakeDrag()
-     */
-    public boolean isFakeDragging() {
-        return mFakeDragging;
-    }
-
-    private void onSecondaryPointerUp(MotionEvent ev) {
-        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
-        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
-        if (pointerId == mActivePointerId) {
-            // This was our active pointer going up. Choose a new
-            // active pointer and adjust accordingly.
-            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
-            mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);
-            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
-            if (mVelocityTracker != null) {
-                mVelocityTracker.clear();
-            }
-        }
-    }
-
-    private void endDrag() {
-        mIsBeingDragged = false;
-        mIsUnableToDrag = false;
-
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-    }
-
-    private void setScrollingCacheEnabled(boolean enabled) {
-        if (mScrollingCacheEnabled != enabled) {
-            mScrollingCacheEnabled = enabled;
-            if (USE_CACHE) {
-                final int size = getChildCount();
-                for (int i = 0; i < size; ++i) {
-                    final View child = getChildAt(i);
-                    if (child.getVisibility() != GONE) {
-                        child.setDrawingCacheEnabled(enabled);
-                    }
-                }
-            }
-        }
-    }
-
-    public boolean internalCanScrollVertically(int direction) {
-        if (mAdapter == null) {
-            return false;
-        }
-
-        final int height = getClientHeight();
-        final int scrollY = getScrollY();
-        if (direction < 0) {
-            return (scrollY > (int) (height * mFirstOffset));
-        } else if (direction > 0) {
-            return (scrollY < (int) (height * mLastOffset));
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Tests scrollability within child views of v given a delta of dx.
-     *
-     * @param v      View to test for horizontal scrollability
-     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
-     *               or just its children (false).
-     * @param dy     Delta scrolled in pixels
-     * @param x      X coordinate of the active touch point
-     * @param y      Y coordinate of the active touch point
-     * @return true if child views of v can be scrolled by delta of dx.
-     */
-    protected boolean canScroll(View v, boolean checkV, int dy, int x, int y) {
-        if (v instanceof ViewGroup) {
-            final ViewGroup group = (ViewGroup) v;
-            final int scrollX = v.getScrollX();
-            final int scrollY = v.getScrollY();
-            final int count = group.getChildCount();
-            // Count backwards - let topmost views consume scroll distance first.
-            for (int i = count - 1; i >= 0; i--) {
-                // TODO: Add versioned support here for transformed views.
-                // This will not work for transformed views in Honeycomb+
-                final View child = group.getChildAt(i);
-                if (y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
-                        x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
-                        canScroll(child, true, dy, x + scrollX - child.getLeft(),
-                                y + scrollY - child.getTop())) {
-                    return true;
-                }
-            }
-        }
-
-        return checkV && ViewCompat.canScrollVertically(v, -dy);
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        // Let the focused view and/or our descendants get the key first
-        return super.dispatchKeyEvent(event) || executeKeyEvent(event);
-    }
-
-    /**
-     * You can call this function yourself to have the scroll view perform
-     * scrolling from a key event, just as if the event had been dispatched to
-     * it by the view hierarchy.
-     *
-     * @param event The key event to execute.
-     * @return Return true if the event was handled, else false.
-     */
-    public boolean executeKeyEvent(KeyEvent event) {
-        boolean handled = false;
-        if (event.getAction() == KeyEvent.ACTION_DOWN) {
-            switch (event.getKeyCode()) {
-                case KeyEvent.KEYCODE_DPAD_LEFT:
-                    handled = arrowScroll(FOCUS_LEFT);
-                    break;
-                case KeyEvent.KEYCODE_DPAD_RIGHT:
-                    handled = arrowScroll(FOCUS_RIGHT);
-                    break;
-                case KeyEvent.KEYCODE_TAB:
-                    if (Build.VERSION.SDK_INT >= 11) {
-                        if (event.hasNoModifiers()) {
-                            handled = this.arrowScroll(2);
-                        } else if (event.hasModifiers(1)) {
-                            handled = this.arrowScroll(1);
-                        }
-                    }
-                    break;
-            }
-        }
-        return handled;
-    }
-
-    public boolean arrowScroll(int direction) {
-        View currentFocused = findFocus();
-        if (currentFocused == this) {
-            currentFocused = null;
-        } else if (currentFocused != null) {
-            boolean isChild = false;
-            for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
-                 parent = parent.getParent()) {
-                if (parent == this) {
-                    isChild = true;
-                    break;
-                }
-            }
-            if (!isChild) {
-                // This would cause the focus search down below to fail in fun ways.
-                final StringBuilder sb = new StringBuilder();
-                sb.append(currentFocused.getClass().getSimpleName());
-                for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
-                     parent = parent.getParent()) {
-                    sb.append(" => ").append(parent.getClass().getSimpleName());
-                }
-                Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
-                        "current focused view " + sb.toString());
-                currentFocused = null;
-            }
-        }
-
-        boolean handled = false;
-
-        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
-                direction);
-        if (nextFocused != null && nextFocused != currentFocused) {
-            if (direction == View.FOCUS_UP) {
-                // If there is nothing to the left, or this is causing us to
-                // jump to the right, then what we really want to do is page left.
-                final int nextTop = getChildRectInPagerCoordinates(mTempRect, nextFocused).top;
-                final int currTop = getChildRectInPagerCoordinates(mTempRect, currentFocused).top;
-                if (currentFocused != null && nextTop >= currTop) {
-                    handled = pageUp();
-                } else {
-                    handled = nextFocused.requestFocus();
-                }
-            } else if (direction == View.FOCUS_DOWN) {
-                // If there is nothing to the right, or this is causing us to
-                // jump to the left, then what we really want to do is page right.
-                final int nextDown = getChildRectInPagerCoordinates(mTempRect, nextFocused).bottom;
-                final int currDown = getChildRectInPagerCoordinates(mTempRect, currentFocused).bottom;
-                if (currentFocused != null && nextDown <= currDown) {
-                    handled = pageDown();
-                } else {
-                    handled = nextFocused.requestFocus();
-                }
-            }
-        } else if (direction == FOCUS_UP || direction == FOCUS_BACKWARD) {
-            // Trying to move left and nothing there; try to page.
-            handled = pageUp();
-        } else if (direction == FOCUS_DOWN || direction == FOCUS_FORWARD) {
-            // Trying to move right and nothing there; try to page.
-            handled = pageDown();
-        }
-        if (handled) {
-            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
-        }
-        return handled;
-    }
-
-    private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
-        if (outRect == null) {
-            outRect = new Rect();
-        }
-        if (child == null) {
-            outRect.set(0, 0, 0, 0);
-            return outRect;
-        }
-        outRect.left = child.getLeft();
-        outRect.right = child.getRight();
-        outRect.top = child.getTop();
-        outRect.bottom = child.getBottom();
-
-        ViewParent parent = child.getParent();
-        while (parent instanceof ViewGroup && parent != this) {
-            final ViewGroup group = (ViewGroup) parent;
-            outRect.left += group.getLeft();
-            outRect.right += group.getRight();
-            outRect.top += group.getTop();
-            outRect.bottom += group.getBottom();
-
-            parent = group.getParent();
-        }
-        return outRect;
-    }
-
-    boolean pageUp() {
-        if (mCurItem > 0) {
-            setCurrentItem(mCurItem - 1, true);
-            return true;
-        }
-        return false;
-    }
-
-    boolean pageDown() {
-        if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {
-            setCurrentItem(mCurItem + 1, true);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * We only want the current page that is being shown to be focusable.
-     */
-    @Override
-    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
-        final int focusableCount = views.size();
-
-        final int descendantFocusability = getDescendantFocusability();
-
-        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
-            for (int i = 0; i < getChildCount(); i++) {
-                final View child = getChildAt(i);
-                if (child.getVisibility() == VISIBLE) {
-                    ItemInfo ii = infoForChild(child);
-                    if (ii != null && ii.position == mCurItem) {
-                        child.addFocusables(views, direction, focusableMode);
-                    }
-                }
-            }
-        }
-
-        // we add ourselves (if focusable) in all cases except for when we are
-        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
-        // to avoid the focus search finding layouts when a more precise search
-        // among the focusable children would be more interesting.
-        if (
-                descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
-                        // No focusable descendants
-                        (focusableCount == views.size())) {
-            // Note that we can't call the superclass here, because it will
-            // add all views in.  So we need to do the same thing View does.
-            if (!isFocusable()) {
-                return;
-            }
-            if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
-                    isInTouchMode() && !isFocusableInTouchMode()) {
-                return;
-            }
-            if (views != null) {
-                views.add(this);
-            }
-        }
-    }
-
-    /**
-     * We only want the current page that is being shown to be touchable.
-     */
-    @Override
-    public void addTouchables(ArrayList<View> views) {
-        // Note that we don't call super.addTouchables(), which means that
-        // we don't call View.addTouchables().  This is okay because a ViewPager
-        // is itself not touchable.
-        for (int i = 0; i < getChildCount(); i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() == VISIBLE) {
-                ItemInfo ii = infoForChild(child);
-                if (ii != null && ii.position == mCurItem) {
-                    child.addTouchables(views);
-                }
-            }
-        }
-    }
-
-    /**
-     * We only want the current page that is being shown to be focusable.
-     */
-    @Override
-    protected boolean onRequestFocusInDescendants(int direction,
-                                                  Rect previouslyFocusedRect) {
-        int index;
-        int increment;
-        int end;
-        int count = getChildCount();
-        if ((direction & FOCUS_FORWARD) != 0) {
-            index = 0;
-            increment = 1;
-            end = count;
-        } else {
-            index = count - 1;
-            increment = -1;
-            end = -1;
-        }
-        for (int i = index; i != end; i += increment) {
-            View child = getChildAt(i);
-            if (child.getVisibility() == VISIBLE) {
-                ItemInfo ii = infoForChild(child);
-                if (ii != null && ii.position == mCurItem) {
-                    if (child.requestFocus(direction, previouslyFocusedRect)) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        // Dispatch scroll events from this ViewPager.
-        if (event.getEventType() == TYPE_VIEW_SCROLLED) {
-            return super.dispatchPopulateAccessibilityEvent(event);
-        }
-
-        // Dispatch all other accessibility events from the current page.
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() == VISIBLE) {
-                final ItemInfo ii = infoForChild(child);
-                if (ii != null && ii.position == mCurItem &&
-                        child.dispatchPopulateAccessibilityEvent(event)) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    @Override
-    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams();
-    }
-
-    @Override
-    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return generateDefaultLayoutParams();
-    }
-
-    @Override
-    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
-        return p instanceof LayoutParams && super.checkLayoutParams(p);
-    }
-
-    @Override
-    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new LayoutParams(getContext(), attrs);
-    }
-
-    class MyAccessibilityDelegate extends AccessibilityDelegateCompat {
-
-        @Override
-        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
-            super.onInitializeAccessibilityEvent(host, event);
-            event.setClassName(ViewPager.class.getName());
-            final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain();
-            recordCompat.setScrollable(canScroll());
-            if (event.getEventType() == TYPE_VIEW_SCROLLED
-                    && mAdapter != null) {
-                recordCompat.setItemCount(mAdapter.getCount());
-                recordCompat.setFromIndex(mCurItem);
-                recordCompat.setToIndex(mCurItem);
-            }
-        }
-
-        @Override
-        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
-            super.onInitializeAccessibilityNodeInfo(host, info);
-            info.setClassName(ViewPager.class.getName());
-            info.setScrollable(canScroll());
-            if (internalCanScrollVertically(1)) {
-                info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
-            }
-            if (internalCanScrollVertically(-1)) {
-                info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
-            }
-        }
-
-        @Override
-        public boolean performAccessibilityAction(View host, int action, Bundle args) {
-            if (super.performAccessibilityAction(host, action, args)) {
-                return true;
-            }
-            switch (action) {
-                case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
-                    if (internalCanScrollVertically(1)) {
-                        setCurrentItem(mCurItem + 1);
-                        return true;
-                    }
-                }
-                return false;
-                case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
-                    if (internalCanScrollVertically(-1)) {
-                        setCurrentItem(mCurItem - 1);
-                        return true;
-                    }
-                }
-                return false;
-            }
-            return false;
-        }
-
-        private boolean canScroll() {
-            return (mAdapter != null) && (mAdapter.getCount() > 1);
-        }
-    }
-
-    private class PagerObserver extends DataSetObserver {
-        @Override
-        public void onChanged() {
-            dataSetChanged();
-        }
-
-        @Override
-        public void onInvalidated() {
-            dataSetChanged();
-        }
-    }
-
-    /**
-     * Layout parameters that should be supplied for views added to a
-     * ViewPager.
-     */
-    public static class LayoutParams extends ViewGroup.LayoutParams {
-        /**
-         * true if this view is a decoration on the pager itself and not
-         * a view supplied by the adapter.
-         */
-        public boolean isDecor;
-
-        /**
-         * Gravity setting for use on decor views only:
-         * Where to position the view page within the overall ViewPager
-         * container; constants are defined in {@link Gravity}.
-         */
-        public int gravity;
-
-        /**
-         * Width as a 0-1 multiplier of the measured pager width
-         */
-        float heightFactor = 0.f;
-
-        /**
-         * true if this view was added during layout and needs to be measured
-         * before being positioned.
-         */
-        boolean needsMeasure;
-
-        /**
-         * Adapter position this view is for if !isDecor
-         */
-        int position;
-
-        /**
-         * Current child index within the ViewPager that this view occupies
-         */
-        int childIndex;
-
-        public LayoutParams() {
-            super(FILL_PARENT, FILL_PARENT);
-        }
-
-        public LayoutParams(Context context, AttributeSet attrs) {
-            super(context, attrs);
-
-            final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
-            gravity = a.getInteger(0, Gravity.TOP);
-            a.recycle();
-        }
-    }
-
-    static class ViewPositionComparator implements Comparator<View> {
-        @Override
-        public int compare(View lhs, View rhs) {
-            final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
-            final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
-            if (llp.isDecor != rlp.isDecor) {
-                return llp.isDecor ? 1 : -1;
-            }
-            return llp.position - rlp.position;
-        }
-    }
-}

+ 1 - 1
Demo/src/main/res/layout/activity_tiktok2.xml

@@ -5,7 +5,7 @@
     android:layout_height="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
     android:orientation="vertical">
 
 
-    <org.yczbj.ycvideoplayer.newPlayer.tiktok.VerticalViewPager
+    <com.yc.pagerlib.pager.VerticalViewPager
         android:id="@+id/vvp"
         android:id="@+id/vvp"
         android:layout_width="match_parent"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
         android:layout_height="match_parent" />

+ 2 - 7
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/controller/BaseVideoController.java

@@ -68,31 +68,24 @@ public abstract class BaseVideoController extends FrameLayout implements InterVi
 
 
     //播放器包装类,集合了MediaPlayerControl的api和IVideoController的api
     //播放器包装类,集合了MediaPlayerControl的api和IVideoController的api
     protected ControlWrapper mControlWrapper;
     protected ControlWrapper mControlWrapper;
-
     @Nullable
     @Nullable
     protected Activity mActivity;
     protected Activity mActivity;
-
     //控制器是否处于显示状态
     //控制器是否处于显示状态
     protected boolean mShowing;
     protected boolean mShowing;
-
     //是否处于锁定状态
     //是否处于锁定状态
     protected boolean mIsLocked;
     protected boolean mIsLocked;
-
     //播放视图隐藏超时
     //播放视图隐藏超时
     protected int mDefaultTimeout = 5000;
     protected int mDefaultTimeout = 5000;
-
     //是否开启根据屏幕方向进入/退出全屏
     //是否开启根据屏幕方向进入/退出全屏
     private boolean mEnableOrientation;
     private boolean mEnableOrientation;
     //屏幕方向监听辅助类
     //屏幕方向监听辅助类
     protected OrientationHelper mOrientationHelper;
     protected OrientationHelper mOrientationHelper;
-
     //用户设置是否适配刘海屏
     //用户设置是否适配刘海屏
     private boolean mAdaptCutout;
     private boolean mAdaptCutout;
     //是否有刘海
     //是否有刘海
     private Boolean mHasCutout;
     private Boolean mHasCutout;
     //刘海的高度
     //刘海的高度
     private int mCutoutHeight;
     private int mCutoutHeight;
-
     //是否开始刷新进度
     //是否开始刷新进度
     private boolean mIsStartProgress;
     private boolean mIsStartProgress;
 
 
@@ -138,10 +131,12 @@ public abstract class BaseVideoController extends FrameLayout implements InterVi
                 postDelayed(new Runnable() {
                 postDelayed(new Runnable() {
                     @Override
                     @Override
                     public void run() {
                     public void run() {
+                        //检查系统是否开启自动旋转
                         mOrientationHelper.enable();
                         mOrientationHelper.enable();
                     }
                     }
                 }, 800);
                 }, 800);
             } else {
             } else {
+                //取消监听
                 mOrientationHelper.disable();
                 mOrientationHelper.disable();
             }
             }
         }
         }

+ 7 - 4
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/player/AudioFocusHelper.java

@@ -60,7 +60,7 @@ public final class AudioFocusHelper implements AudioManager.OnAudioFocusChangeLi
         }
         }
         switch (focusChange) {
         switch (focusChange) {
             case AudioManager.AUDIOFOCUS_GAIN:
             case AudioManager.AUDIOFOCUS_GAIN:
-                //获得焦点
+                //重新获得焦点
             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
                 //暂时获得焦点
                 //暂时获得焦点
                 if (mStartRequested || mPausedForLoss) {
                 if (mStartRequested || mPausedForLoss) {
@@ -73,16 +73,16 @@ public final class AudioFocusHelper implements AudioManager.OnAudioFocusChangeLi
                     videoView.setVolume(1.0f, 1.0f);
                     videoView.setVolume(1.0f, 1.0f);
                 break;
                 break;
             case AudioManager.AUDIOFOCUS_LOSS:
             case AudioManager.AUDIOFOCUS_LOSS:
-                //焦点丢失
+                //焦点丢失,这个是永久丢失焦点,如被其他播放器抢占
             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
-                //焦点暂时丢失
+                //焦点暂时丢失,,如来电
                 if (videoView.isPlaying()) {
                 if (videoView.isPlaying()) {
                     mPausedForLoss = true;
                     mPausedForLoss = true;
                     videoView.pause();
                     videoView.pause();
                 }
                 }
                 break;
                 break;
             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
             case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
-                //此时需降低音量
+                //此时需降低音量,瞬间丢失焦点,如通知
                 if (videoView.isPlaying() && !videoView.isMute()) {
                 if (videoView.isPlaying() && !videoView.isMute()) {
                     videoView.setVolume(0.1f, 0.1f);
                     videoView.setVolume(0.1f, 0.1f);
                 }
                 }
@@ -95,13 +95,16 @@ public final class AudioFocusHelper implements AudioManager.OnAudioFocusChangeLi
      */
      */
     public void requestFocus() {
     public void requestFocus() {
         if (mCurrentFocus == AudioManager.AUDIOFOCUS_GAIN) {
         if (mCurrentFocus == AudioManager.AUDIOFOCUS_GAIN) {
+            //如果已经是获得焦点,则直接返回
             return;
             return;
         }
         }
         if (mAudioManager == null) {
         if (mAudioManager == null) {
             return;
             return;
         }
         }
+        //请求重新获取焦点
         int status = mAudioManager.requestAudioFocus(this,
         int status = mAudioManager.requestAudioFocus(this,
                 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
                 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+        //焦点更改请求成功
         if (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == status) {
         if (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == status) {
             mCurrentFocus = AudioManager.AUDIOFOCUS_GAIN;
             mCurrentFocus = AudioManager.AUDIOFOCUS_GAIN;
             return;
             return;

+ 2 - 2
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/player/VideoPlayer.java

@@ -22,7 +22,7 @@ import org.yczbj.ycvideoplayerlib.controller.BaseVideoController;
 import com.yc.kernel.inter.AbstractVideoPlayer;
 import com.yc.kernel.inter.AbstractVideoPlayer;
 import com.yc.kernel.factory.PlayerFactory;
 import com.yc.kernel.factory.PlayerFactory;
 
 
-import org.yczbj.ycvideoplayerlib.surface.ISurfaceView;
+import org.yczbj.ycvideoplayerlib.surface.InterSurfaceView;
 import org.yczbj.ycvideoplayerlib.surface.SurfaceFactory;
 import org.yczbj.ycvideoplayerlib.surface.SurfaceFactory;
 import org.yczbj.ycvideoplayerlib.tool.BaseToast;
 import org.yczbj.ycvideoplayerlib.tool.BaseToast;
 import org.yczbj.ycvideoplayerlib.tool.PlayerUtils;
 import org.yczbj.ycvideoplayerlib.tool.PlayerUtils;
@@ -69,7 +69,7 @@ public class VideoPlayer<P extends AbstractVideoPlayer> extends FrameLayout
      */
      */
     protected FrameLayout mPlayerContainer;
     protected FrameLayout mPlayerContainer;
 
 
-    protected ISurfaceView mRenderView;
+    protected InterSurfaceView mRenderView;
     protected SurfaceFactory mRenderViewFactory;
     protected SurfaceFactory mRenderViewFactory;
     protected int mCurrentScreenScaleType;
     protected int mCurrentScreenScaleType;
     protected int[] mVideoSize = {0, 0};
     protected int[] mVideoSize = {0, 0};

+ 1 - 1
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/ISurfaceView.java → VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/InterSurfaceView.java

@@ -16,7 +16,7 @@ import com.yc.kernel.inter.AbstractVideoPlayer;
  *     revise:
  *     revise:
  * </pre>
  * </pre>
  */
  */
-public interface ISurfaceView {
+public interface InterSurfaceView {
 
 
     /**
     /**
      * 关联AbstractPlayer
      * 关联AbstractPlayer

+ 18 - 1
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/MeasureHelper.java

@@ -4,7 +4,15 @@ import android.view.View;
 
 
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
 import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
 
 
-
+/**
+ * <pre>
+ *     @author yangchong
+ *     blog  : https://github.com/yangchong211
+ *     time  : 2018/9/21
+ *     desc  : 帮助类
+ *     revise:
+ * </pre>
+ */
 public class MeasureHelper {
 public class MeasureHelper {
 
 
     private int mVideoWidth;
     private int mVideoWidth;
@@ -12,10 +20,19 @@ public class MeasureHelper {
     private int mCurrentScreenScale;
     private int mCurrentScreenScale;
     private int mVideoRotationDegree;
     private int mVideoRotationDegree;
 
 
+    /**
+     * 设置视频旋转角度
+     * @param videoRotationDegree           角度值
+     */
     public void setVideoRotation(int videoRotationDegree) {
     public void setVideoRotation(int videoRotationDegree) {
         mVideoRotationDegree = videoRotationDegree;
         mVideoRotationDegree = videoRotationDegree;
     }
     }
 
 
+    /**
+     * 设置视频宽高
+     * @param width                         宽
+     * @param height                        高
+     */
     public void setVideoSize(int width, int height) {
     public void setVideoSize(int width, int height) {
         mVideoWidth = width;
         mVideoWidth = width;
         mVideoHeight = height;
         mVideoHeight = height;

+ 11 - 2
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/RenderSurfaceView.java

@@ -37,12 +37,19 @@ import com.yc.kernel.inter.AbstractVideoPlayer;
  *     revise:
  *     revise:
  * </pre>
  * </pre>
  */
  */
-public class RenderSurfaceView extends SurfaceView implements ISurfaceView{
+public class RenderSurfaceView extends SurfaceView implements InterSurfaceView {
 
 
     /**
     /**
      * 优点:可以在一个独立的线程中进行绘制,不会影响主线程;使用双缓冲机制,播放视频时画面更流畅
      * 优点:可以在一个独立的线程中进行绘制,不会影响主线程;使用双缓冲机制,播放视频时画面更流畅
      * 缺点:Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,
      * 缺点:Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,
      *      也不能放在其它ViewGroup中。SurfaceView 不能嵌套使用。
      *      也不能放在其它ViewGroup中。SurfaceView 不能嵌套使用。
+     *
+     * SurfaceView双缓冲
+     *      1.SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas。
+     *      2.每次实际显示的是frontCanvas,backCanvas存储的是上一次更改前的视图,当使用lockCanvas()获取画布时,
+     *        得到的实际上是backCanvas而不是正在显示的frontCanvas,之后你在获取到的backCanvas上绘制新视图,
+     *        再unlockCanvasAndPost(canvas)此视图,那么上传的这张canvas将替换原来的frontCanvas作为新的frontCanvas,
+     *        原来的frontCanvas将切换到后台作为backCanvas。
      */
      */
 
 
     private MeasureHelper mMeasureHelper;
     private MeasureHelper mMeasureHelper;
@@ -65,7 +72,9 @@ public class RenderSurfaceView extends SurfaceView implements ISurfaceView{
 
 
     private void init(Context context){
     private void init(Context context){
         mMeasureHelper = new MeasureHelper();
         mMeasureHelper = new MeasureHelper();
-        getHolder().addCallback(callback);
+        SurfaceHolder holder = this.getHolder();
+        //holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+        holder.addCallback(callback);
     }
     }
 
 
     /**
     /**

+ 59 - 46
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/RenderTextureView.java

@@ -14,9 +14,18 @@ import androidx.annotation.Nullable;
 import com.yc.kernel.inter.AbstractVideoPlayer;
 import com.yc.kernel.inter.AbstractVideoPlayer;
 
 
 
 
+/**
+ * <pre>
+ *     @author yangchong
+ *     blog  : https://github.com/yangchong211
+ *     time  : 2018/9/21
+ *     desc  : 重写TextureView,适配视频的宽高和旋转
+ *     revise: 1.继承View,具有view的特性,比如移动,旋转,缩放,动画等变化。支持截图
+ *             8.必须在硬件加速的窗口中使用,占用内存比SurfaceView高,在5.0以前在主线程渲染,5.0以后有单独的渲染线程。
+ * </pre>
+ */
 @SuppressLint("ViewConstructor")
 @SuppressLint("ViewConstructor")
-public class RenderTextureView extends TextureView implements ISurfaceView,
-        TextureView.SurfaceTextureListener {
+public class RenderTextureView extends TextureView implements InterSurfaceView {
 
 
     private MeasureHelper mMeasureHelper;
     private MeasureHelper mMeasureHelper;
     private SurfaceTexture mSurfaceTexture;
     private SurfaceTexture mSurfaceTexture;
@@ -32,7 +41,7 @@ public class RenderTextureView extends TextureView implements ISurfaceView,
 
 
     private void init(Context context){
     private void init(Context context){
         mMeasureHelper = new MeasureHelper();
         mMeasureHelper = new MeasureHelper();
-        setSurfaceTextureListener(this);
+        setSurfaceTextureListener(listener);
     }
     }
 
 
     /**
     /**
@@ -115,25 +124,6 @@ public class RenderTextureView extends TextureView implements ISurfaceView,
         setMeasuredDimension(measuredSize[0], measuredSize[1]);
         setMeasuredDimension(measuredSize[0], measuredSize[1]);
     }
     }
 
 
-    /**
-     * SurfaceTexture准备就绪
-     * @param surfaceTexture            surface
-     * @param width                     WIDTH
-     * @param height                    HEIGHT
-     */
-    @Override
-    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
-        if (mSurfaceTexture != null) {
-            setSurfaceTexture(mSurfaceTexture);
-        } else {
-            mSurfaceTexture = surfaceTexture;
-            mSurface = new Surface(surfaceTexture);
-            if (mMediaPlayer != null) {
-                mMediaPlayer.setSurface(mSurface);
-            }
-        }
-    }
-
     /**
     /**
      * 记得一定要重新写这个方法,如果角度发生了变化,就重新绘制布局
      * 记得一定要重新写这个方法,如果角度发生了变化,就重新绘制布局
      * 设置视频旋转角度
      * 设置视频旋转角度
@@ -147,33 +137,56 @@ public class RenderTextureView extends TextureView implements ISurfaceView,
         }
         }
     }
     }
 
 
+    private SurfaceTextureListener listener = new SurfaceTextureListener() {
+        /**
+         * SurfaceTexture准备就绪
+         * @param surfaceTexture            surface
+         * @param width                     WIDTH
+         * @param height                    HEIGHT
+         */
+        @Override
+        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+            if (mSurfaceTexture != null) {
+                setSurfaceTexture(mSurfaceTexture);
+            } else {
+                mSurfaceTexture = surfaceTexture;
+                mSurface = new Surface(surfaceTexture);
+                if (mMediaPlayer != null) {
+                    mMediaPlayer.setSurface(mSurface);
+                }
+            }
+        }
 
 
-    /**
-     * SurfaceTexture缓冲大小变化
-     * @param surface                   surface
-     * @param width                     WIDTH
-     * @param height                    HEIGHT
-     */
-    @Override
-    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        /**
+         * SurfaceTexture缓冲大小变化
+         * @param surface                   surface
+         * @param width                     WIDTH
+         * @param height                    HEIGHT
+         */
+        @Override
+        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
 
 
-    }
+        }
+
+        /**
+         * SurfaceTexture即将被销毁
+         * @param surface                   surface
+         */
+        @Override
+        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+            return false;
+        }
+
+        /**
+         * SurfaceTexture通过updateImage更新
+         * @param surface                   surface
+         */
+        @Override
+        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+
+        }
+    };
 
 
-    /**
-     * SurfaceTexture即将被销毁
-     * @param surface                   surface
-     */
-    @Override
-    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
-        return false;
-    }
 
 
-    /**
-     * SurfaceTexture通过updateImage更新
-     * @param surface                   surface
-     */
-    @Override
-    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
 
 
-    }
 }
 }

+ 1 - 1
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/SurfaceFactory.java

@@ -13,6 +13,6 @@ import android.content.Context;
  */
  */
 public abstract class SurfaceFactory {
 public abstract class SurfaceFactory {
 
 
-    public abstract ISurfaceView createRenderView(Context context);
+    public abstract InterSurfaceView createRenderView(Context context);
 
 
 }
 }

+ 3 - 2
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/SurfaceViewFactory.java

@@ -18,7 +18,8 @@ public class SurfaceViewFactory extends SurfaceFactory {
     }
     }
 
 
     @Override
     @Override
-    public ISurfaceView createRenderView(Context context) {
-        return new RenderTextureView(context);
+    public InterSurfaceView createRenderView(Context context) {
+        //创建SurfaceView
+        return new RenderSurfaceView(context);
     }
     }
 }
 }

+ 2 - 1
VideoPlayer/src/main/java/org/yczbj/ycvideoplayerlib/surface/TextureViewFactory.java

@@ -18,7 +18,8 @@ public class TextureViewFactory extends SurfaceFactory {
     }
     }
 
 
     @Override
     @Override
-    public ISurfaceView createRenderView(Context context) {
+    public InterSurfaceView createRenderView(Context context) {
+        //创建TextureView
         return new RenderTextureView(context);
         return new RenderTextureView(context);
     }
     }
 }
 }

+ 13 - 2
read/04.视频播放器封装思路.md

@@ -163,7 +163,7 @@
 
 
 
 
 ### 06.如何简单使用
 ### 06.如何简单使用
-### 6.1 播放单个视频
+#### 6.1 播放单个视频
 - 必须需要的四步骤代码如下所示
 - 必须需要的四步骤代码如下所示
     ``` java
     ``` java
     //创建基础视频播放器,一般播放器的功能
     //创建基础视频播放器,一般播放器的功能
@@ -178,8 +178,20 @@
 - 只需要四步操作即可,非常简单。这样就可以满足一个基础的视频播放器
 - 只需要四步操作即可,非常简单。这样就可以满足一个基础的视频播放器
     - 具体逻辑可以看:BasisVideoController
     - 具体逻辑可以看:BasisVideoController
 - 如何添加只定义视图,非常方便。AdControlView需要实现InterControlView接口才可以
 - 如何添加只定义视图,非常方便。AdControlView需要实现InterControlView接口才可以
+    - 注意,对于自定义播放器视图,该类主要是UI相关的操作。业务上的逻辑不要写到该类中,尽量通过接口的监听给外部开发者,比如自定义广告视图,点击广告跳转,和直接跳转暴露出来,代码如下所示。
     ``` java
     ``` java
     AdControlView adControlView = new AdControlView(this);
     AdControlView adControlView = new AdControlView(this);
+    adControlView.setListener(new AdControlView.AdControlListener() {
+        @Override
+        public void onAdClick() {
+            BaseToast.showRoundRectToast( "广告点击跳转");
+        }
+
+        @Override
+        public void onSkipAd() {
+            playVideo();
+        }
+    });
     controller.addControlComponent(adControlView);
     controller.addControlComponent(adControlView);
     ```
     ```
 - 要是一个页面播放多个视频怎么办
 - 要是一个页面播放多个视频怎么办
@@ -187,7 +199,6 @@
     - 如果是开启的音频焦点改变监听,那么播放该视频的时候,就会停止其他音视频的播放操作。类似,你听音乐,这个时候去看视频,那么音乐就暂停呢
     - 如果是开启的音频焦点改变监听,那么播放该视频的时候,就会停止其他音视频的播放操作。类似,你听音乐,这个时候去看视频,那么音乐就暂停呢
 
 
 
 
-
 #### 6.2 列表播放视频
 #### 6.2 列表播放视频
 - 关于列表播放视频,该案例支持
 - 关于列表播放视频,该案例支持
     - 列表页面有多个item
     - 列表页面有多个item

+ 147 - 3
read/11.视频播放器音频焦点抢占.md

@@ -1,9 +1,153 @@
-# 边播边缓存
+# 11.视频播放器音频焦点抢占
+#### 目录介绍
+- 01.先说一下音视频焦点问题
+- 02.为何处理音视频焦点抢占
+- 03.处理音频焦点思路步骤
+- 04.何时才会失去音视频焦点
+- 05.请求和放弃音频焦点
+- 06.音视频焦点变化处理逻辑
+
+
+
+### 00.视频播放器通用框架
+- 基础封装视频播放器player,可以在ExoPlayer、MediaPlayer,声网RTC视频播放器内核,原生MediaPlayer可以自由切换
+- 对于视图状态切换和后期维护拓展,避免功能和业务出现耦合。比如需要支持播放器UI高度定制,而不是该lib库中UI代码
+- 针对视频播放,音频播放,播放回放,以及视频直播的功能。使用简单,代码拓展性强,封装性好,主要是和业务彻底解耦,暴露接口监听给开发者处理业务具体逻辑
+- 该播放器整体架构:播放器内核(自由切换) +  视频播放器 + 边播边缓存 + 高度定制播放器UI视图层
+- 项目地址:https://github.com/yangchong211/YCVideoPlayer
+- 关于视频播放器整体功能介绍文档:https://juejin.im/post/6883457444752654343
+
+
+
+
+### 01.先说一下音视频焦点问题
+- 如果手机上安装了两个音频播放器,当一个正在播放的时候,打开第二个播放歌曲,有没有发现第一个自动暂停了……
+- 如果你在听音频的同时,又去打开了其它视频APP,你会发现音频APP暂停播放了……
+- 如果你正在听音频或者看视频时,来电话了,那么音视频便会暂停。挂了电话后音乐又继续播放,视频则需要点击按钮播放,是不是很奇怪
+- 当你收到消息,比如微信消息,并且有消息声音的时候,那么听音频的那一瞬间,音频的声音会变小了,然后过会儿又恢复了。是不是很有意思。
+- 别蒙圈,这个就叫做音频捕获和丢弃焦点。
+
+
+
+### 02.为何处理音视频焦点抢占
+- 如果不处理捕获与丢弃音频焦点的话,那么同时开几个音视频播放器,就会出现多个声音。那样会很嘈杂,一般线上的APP都会做这个处理,不过一些GitHub案例demo中一般没处理。
+- 为了协调设备的音频输出,android提出了Audio Focus机机制,获取audio focus必须调用AudioManager的requestAudioFocus()方法。
+
+
+### 03.处理音频焦点思路步骤
+- 简单来说,就是这三步逻辑方法
+    - 在onCreate方法中调用初始化方法
+    - 在播放音视频的时候开始请求捕获音频焦点
+    - 在音视频销毁的时候开始丢弃音频焦点
+
+
+### 04.何时才会失去音视频焦点
+- 失去焦点有三种类型
+    - 1.失去短暂焦点
+    - 2.失去永久焦点
+    - 3.Ducking
+- 失去焦点原理说明
+    - 当重新获得焦点的时候,如果通话结束,恢复播放;获取音量并且恢复音量。这个情景应该经常遇到。
+    - 当永久丢失焦点,比如同时打开播放器,则停止或者暂停播放,否则出现两个声音
+    - 当短暂丢失焦点,比如比如来了电话或者微信视频音频聊天等等,则暂停或者停止播放
+    - 当瞬间丢失焦点,比如手机来了通知。前提是你的通知是震动或者声音时,会短暂地将音量减小一半。当然你也可以减小三分之一,哈哈!
+
+
+### 05.请求和放弃音频焦点
+- 首先获取AudioManager对象
+    ``` java
+    mAudioManager = (AudioManager) content.getSystemService(AUDIO_SERVICE);
+    ```
+- 请求和放弃音频焦点
+    - AudioFocus这个其实是音频焦点,一般情况下音视频播放器都会处理这个音频焦点的,在其丢失音频焦点的情况会将音频暂停或者停止的逻辑的,等到再次获取到音频焦点的情况下会再次恢复播放的。
+    - 音频获取焦点可以通过requestAudioFocus()方法获得,在音频焦点成功获取后,该方法会返回AUDIOFOCUS_REQUEST_GRANTED常量,否则,会返回AUDIOFOCUS_REQUEST_FAILED常量。
+    - 音视频失去焦点abandonAudioFocus()方法,这会通知系统您的App不再需要音频焦点,并移除相关OnAudioFocusChangeListener的注册。如果释放的是短暂音调焦点,那么被打断的音频会被继续播放。
+- **代码如下所示**,下面是简单的代码。具体看:AudioFocusHelper类
+    ``` java
+    /**
+     * 请求音频焦点,开始播放时候调用
+     * @return
+     */
+    public boolean requestAudioFocus() {
+        return mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
+                AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+    }
+    
+    /**
+     * 放弃音频焦点,销毁播放时候调用
+     */
+    public void abandonAudioFocus() {
+        mAudioManager.abandonAudioFocus(this);
+    }
+    ```
+
+### 06.音视频焦点变化处理逻辑
+- 当焦点发生变化的时候,可以在这个方法onAudioFocusChange中处理业务逻辑,由于onAudioFocusChange有可能在子线程调用,所以需要切换线程处理逻辑
+    ``` java
+    @Override
+    public void onAudioFocusChange(final int focusChange) {
+        if (mCurrentFocus == focusChange) {
+            return;
+        }
+        //由于onAudioFocusChange有可能在子线程调用,
+        //故通过此方式切换到主线程去执行
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                //处理音频焦点抢占
+                handleAudioFocusChange(focusChange);
+            }
+        });
+        mCurrentFocus = focusChange;
+    }
+    ```
+- 当音频焦点发生变化的时候调用这个方法,在这里可以处理逻辑。详细案例,可以直接参考我的demo
+    ``` java
+    private void handleAudioFocusChange(int focusChange) {
+        final VideoPlayer videoView = mWeakVideoView.get();
+        if (videoView == null) {
+            return;
+        }
+        switch (focusChange) {
+            case AudioManager.AUDIOFOCUS_GAIN:
+                //重新获得焦点
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+                //暂时获得焦点
+                if (mStartRequested || mPausedForLoss) {
+                    videoView.start();
+                    mStartRequested = false;
+                    mPausedForLoss = false;
+                }
+                if (!videoView.isMute())
+                    //恢复音量
+                    videoView.setVolume(1.0f, 1.0f);
+                break;
+            case AudioManager.AUDIOFOCUS_LOSS:
+                //焦点丢失,这个是永久丢失焦点,如被其他播放器抢占
+            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+                //焦点暂时丢失,,如来电
+                if (videoView.isPlaying()) {
+                    mPausedForLoss = true;
+                    videoView.pause();
+                }
+                break;
+            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+                //此时需降低音量,瞬间丢失焦点,如通知
+                if (videoView.isPlaying() && !videoView.isMute()) {
+                    videoView.setVolume(0.1f, 0.1f);
+                }
+                break;
+        }
+    }
+    ```
+- focusChange参数值
+    - 1.AUDIOFOCUS_GAIN:获取audio focus
+    - 2.AUDIOFOCUS_LOSS:失去audio focus很长一段时间,必须停止所有的audio播放,清理资源
+    - 3.AUDIOFOCUS_ LOSS_TRANSIENT:暂时失去audio focus,但是很快就会重新获得,在此状态应该暂停所有音频播放,但是不能清除资源
+    - 4.AUDIOFOCUS_ LOSS_TRANSIENT _CAN_DUCK:暂时失去 audio focus,但是允许持续播放音频(以很小的声音),不需要完全停止播放。
 
 
 
 
 
 
-- https://blog.csdn.net/ta893115871/article/details/71429738
-
 
 
 
 
 
 

+ 0 - 0
read/24.SurfaceView深入学习.md → read/24.1SurfaceView深入学习.md


+ 515 - 0
read/24.2SurfaceView源码分析.md

@@ -0,0 +1,515 @@
+# SurfaceView源码分析
+#### 目录介绍
+- 01.Android中绘制模型
+    - 1.1 软件绘制模型
+    - 1.2 硬件加速绘制模型
+- 02.SurfaceView是什么
+    - 2.1 继承自类View
+    - 2.2 SurfaceView优缺点
+    - 2.3 使用SurfaceView播放视频
+- 03.SurfaceView双缓冲
+    - 3.1 双缓冲机制由来
+    - 3.2 如何理解SurfaceView双缓冲
+- 04.SurfaceView源码分析
+    - 4.1 为何不会阻塞Ui线程
+    - 4.2 SurfaceHolder.Callback
+    - 4.3 SurfaceHolder类源码
+    - 4.4 SurfaceView部分源码
+    - 4.5 看看Surface源码
+- 05.SurfaceView总结
+    - 5.1 使用场景
+    - 5.2 为何使用SurfaceView
+
+
+
+
+### 01.Android中绘制模型
+#### 1.1 软件绘制模型
+- 这里由CPU主导绘图,视图按照以下2个步骤绘图。
+    - 让视图结构(view hierarchy)失效。
+    - 绘制整个视图结构。
+- 当应用程序需要更新它的部分UI时,都会调用内容发生改变的View对象的invalidate()方法。无效(invalidation)消息请求会在View对象层次结构中传递,以便计算出需要重绘的屏幕区域(脏区)。然后,Android系统会在View层次结构中绘制所有的跟脏区相交的区域。但是,这种方法有两个缺点:
+    - 绘制了不需要重绘的视图(与脏区域相交的区域)
+    - 掩盖了一些应用的bug(由于会重绘与脏区域相交的区域)
+- 比如:在View对象的属性发生变化时,如背景色或TextView对象中的文本等,Android系统会自动的调用该View对象的invalidate()方法。
+
+
+
+#### 1.2 硬件加速绘制模型
+- 这里由GPU主导绘图,视图按照以下3个步骤绘图。
+    - 让视图结构失效。
+    - 记录和更新显示列表(Display List)。
+    - 绘制显示列表。
+- 这种模式下,Android系统依然会使用invalidate()方法和draw()方法来请求屏幕更新和展现View对象。但Android系统并不是立即执行绘制命令,而是首先把这些View的绘制函数作为绘制指令记录一个显示列表中,然后再读取显示列表中的绘制指令调用OpenGL相关函数完成实际绘制。另一个优化是,Android系统只需要针对由invalidate()方法调用所标记的View对象的脏区进行记录和更新显示列表。没有失效的View对象就简单重用先前显示列表记录的绘制指令来进行简单的重绘工作。
+- 使用显示列表的目的是,把视图的各种绘制函数翻译成绘制指令保存起来,对于没有发生改变的视图把原先保存的操作指令重新读取出来重放一次就可以了,提高了视图的显示速度。而对于需要重绘的View,则更新显示列表,然后再调用OpenGL完成绘制。  
+- 在这种绘制模型下,我们不能依赖一个视图与脏区(dirty region)相交而导致它的draw()方法被自动调用,所以必须要手动调用该视图的invalidate()方法去更新显示列表。如果忘记这么做可能导致视图在改变后不会发生变化。
+- 硬件加速提高了Android系统显示和刷新的速度,但它也不是万能的,它有三个缺陷:
+    - 兼容性(部分绘制函数不支持或不完全硬件加速
+    - 内存消耗(OpenGL API调用就会占用8MB,而实际上会占用更多内存
+    - 电量消耗(GPU耗电)
+
+
+
+### 02.SurfaceView是什么
+#### 2.1 继承自类View
+- 它继承自类View,因此它本质上是一个View。
+    - 但与普通View不同的是,它有自己的Surface。有自己的Surface,在WMS中有对应的WindowState,在SurfaceFlinger中有Layer。我们知道,一般的Activity包含的多个View会组成View hierachy的树形结构,只有最顶层的DecorView,也就是根结点视图,才是对WMS可见的。这个DecorView在WMS中有一个对应的WindowState。相应地,在SF中对应的Layer。
+    - 一般的View只能在UI线程中绘制,而SurfaceView却能在非UI线程中绘制,或者说SurfaceView则在一个子线程中去更新自己,这样的结果是即使SurfaceView频繁的刷新重绘也不会阻塞主线程导致卡顿甚至ANR。
+- 而SurfaceView自带一个Surface,这个Surface在WMS中有自己对应的WindowState,
+    - 在SF中也会有自己的Layer。虽然在App端它仍在View hierachy中,但在Server端(WMS和SF)中,它与宿主窗口是分离的。这样的好处是对这个Surface的渲染可以放到单独线程去做,渲染时可以有自己的GL context。这对于一些游戏、视频等性能相关的应用非常有益,因为它不会影响主线程对事件的响应。
+- 但它也有缺点,因为这个Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中,一些View中的特性也无法使用。
+
+
+
+#### 2.2 SurfaceView优缺点
+- 优点:
+    - 可以在一个独立的线程中进行绘制,不会影响主线程
+    - 使用双缓冲机制,播放视频时画面更流畅
+- 缺点:
+    - Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中。SurfaceView 不能嵌套使用。
+
+
+
+#### 2.3 使用SurfaceView播放视频
+- SurfaceView+MediaPlayer
+    - 使用SurfaceView播放一个视频流媒体。MediaPlayer播放视频需要SurfaceView的配合,SurfaceView主要用于显示MediaPlayer播放的视频流媒体的画面渲染。
+    - MediaPlayer也提供了相应的方法设置SurfaceView显示图片,只需要为MediaPlayer指定SurfaceView显示图像即可。
+- 大概步骤如下所示
+    - 调用player.setDataSource()方法设置要播放的资源,可以是文件、文件路径、或者URL。
+    - 调用MediaPlayer.setDisplay(holder)设置surfaceHolder,surfaceHolder可以通过surfaceview的getHolder()方法获得。
+    - 调用MediaPlayer.prepare()来准备。
+    - 调用MediaPlayer.start()来播放视频。
+    - 注意:在setDisplay方法中,需要传递一个SurfaceHolder对象,SurfaceHolder可以理解为SurfaceView装载需要显示的一帧帧图像的容器,它可以通过SurfaceHolder.getHolder()方法获得。
+- 代码如下所示,简单型的
+    ```
+    VideoSurfaceView videoSurfaceView = new VideoSurfaceView(mContext);
+    SurfaceHolder holder = videoSurfaceView.getHolder();
+    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+    holder.addCallback(new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+    
+        }
+    
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+    
+        }
+    
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+    
+        }
+    });
+    mMediaPlayer.setDataSource(this, Uri.parse(url));
+    mMediaPlayer.setDisplay(holder);
+    mMediaPlayer.prepare()
+    mMediaPlayer.start()
+    
+    
+    //添加到视图中
+    LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+            ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER);
+    mContainer.addView(videoSurfaceView, 0, params);
+    ```
+
+
+### 03.SurfaceView双缓冲
+#### 3.1 双缓冲机制由来
+- 问题的由来
+    - CPU访问内存的速度要远远快于访问屏幕的速度。如果需要绘制大量复杂的图像时,每次都一个个从内存中读取图形然后绘制到屏幕就会造成多次地访问屏幕,从而导致效率很低。这就跟CPU和内存之间还需要有三级缓存一样,需要提高效率。
+- 第一层缓冲
+    - 在绘制图像时不用上述一个一个绘制的方案,而采用先在内存中将所有的图像都绘制到一个Bitmap对象上,然后一次性将内存中的Bitmap绘制到屏幕,从而提高绘制的效率。Android中View的onDraw()方法已经实现了这一层缓冲。onDraw()方法中不是绘制一点显示一点,而是都绘制完后一次性显示到屏幕。
+- 第二层缓冲
+    - onDraw()方法的Canvas对象是和屏幕关联的,而onDraw()方法是运行在UI线程中的,如果要绘制的图像过于复杂,则有可能导致应用程序卡顿,甚至ANR。因此我们可以先创建一个临时的Canvas对象,将图像都绘制到这个临时的Canvas对象中,绘制完成之后再将这个临时Canvas对象中的内容(也就是一个Bitmap),通过drawBitmap()方法绘制到onDraw()方法中的canvas对象中。这样的话就相当于是一个Bitmap的拷贝过程,比直接绘制效率要高,可以减少对UI线程的阻塞。
+
+
+
+#### 3.2 如何理解SurfaceView双缓冲
+- 双缓冲在运用时可以理解为:
+    - SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储的是上一次更改前的视图,当使用lockCanvas()获取画布时,得到的实际上是backCanvas而不是正在显示的frontCanvas,之后你在获取到的backCanvas上绘制新视图,再unlockCanvasAndPost(canvas)此视图,那么上传的这张canvas将替换原来的frontCanvas作为新的frontCanvas,原来的frontCanvas将切换到后台作为backCanvas。例如,如果你已经先后两次绘制了视图A和B,那么你再调用lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你将重绘的C视图上传,那么C将取代B作为新的frontCanvas显示在SurfaceView上,原来的B则转换为backCanvas。
+- 在网上还看到一段解释
+    - 双缓冲技术是游戏开发中的一个重要的技术。当一个动画争先显示时,程序又在改变它,前面还没有显示完,程序又请求重新绘制,这样屏幕就会不停地闪烁。而双缓冲技术是把要处理的图片在内存中处理好之后,再将其显示在屏幕上。双缓冲主要是为了解决 反复局部刷屏带来的闪烁。把要画的东西先画到一个内存区域里,然后整体的一次性画出来。
+
+
+
+### 04.SurfaceView源码分析
+#### 4.1 为何不会阻塞Ui线程
+- 首先看一下下面代码
+    - 实现 SurfaceHolder.Callback 接口,并且重写里面的三个方法
+    - 在 SurfaceHolder.Callback 的 surfaceCreated 方法中开启一个线程进行图像的绘制
+    - 在 SufaceHolder.Callback 的 surfaceDestroyed 方法中,结束绘制线程并调用 SurfaceHolder 的 removeCallbck 方法
+    - 在绘制线程每帧开始之前,调用 lockCanvas 方法锁住画布进行绘图
+    - 绘制完一帧的数据之后,调用 unlockCanvasAndPost 方法提交数据来显示图像
+    - 用于控制子线程绘制的标记参数,如上面代码中的mIsDrawing变量,需要用volatile关键字修饰,以保证多线程安全。
+    ```
+    /**
+     * 必须实现SurfaceHolder.Callback接口和Runnable接口
+     */
+    public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
+    
+        // 是否绘制
+        private volatile boolean mIsDrawing;
+        // SurfaceView 控制器
+        private SurfaceHolder mSurfaceHolder;
+        // 画笔
+        private Paint mPaint;
+        // 画布
+        private Canvas mCanvas;
+        // 独立的线程
+        private Thread mThread;
+    
+        public MySurfaceView(Context context) {
+            super(context);
+            init();
+        }
+    
+        public MySurfaceView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            init();
+        }
+    
+        public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+            init();
+        }
+    
+        private void init() {
+            mSurfaceHolder = getHolder();
+            // 注册回调事件
+            mSurfaceHolder.addCallback(this);
+    
+            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mPaint.setStyle(Paint.Style.STROKE);
+        }
+    
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            VideoLogUtil.d("onSurfaceCreated");
+            mThread = new Thread(this, "yc");
+        }
+    
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            VideoLogUtil.d("onSurfaceChanged"+format+ "----"+ width+ "----"+ height);
+            //并开启线程
+            mIsDrawing = true;
+            mThread.start();
+        }
+    
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            VideoLogUtil.d("onSurfaceDestroyed");
+            // 不再绘制,移除回调,线程终止
+            mIsDrawing = false;
+            mSurfaceHolder.removeCallback(this);
+            mThread.interrupt();
+        }
+    
+        @Override
+        public void run() {
+            while (mIsDrawing) {
+                VideoLogUtil.d("draw canvas");
+                // 锁定画布,获得画布对象
+                mCanvas = mSurfaceHolder.lockCanvas();
+                try {
+                    //使用画布做具体的绘制
+                    draw();
+                    // 线程休眠 100 ms
+                    Thread.sleep(100);
+                } catch (Exception e) {
+                    VideoLogUtil.d(e.getMessage());
+                } finally {
+                    // 解锁画布,提交绘制,显示内容
+                    if (mCanvas != null) {
+                        mSurfaceHolder.unlockCanvasAndPost(mCanvas);
+                    }
+                }
+            }
+        }
+    
+        private void draw() {
+            //开始绘制
+        }
+    }
+    ```
+- 由于SurfaceView常被用于游戏、视频等场景,绘制操作会相对复杂很多,通常都需要开启子线程,在子线程中执行绘制操作,以免阻塞UI线程。在子线程中,我们通过SurfaceHolder的lockCanvas方法获取Canvas对象来进行具体的绘制操作,此时Canvas对象被当前线程锁定,绘制完成后通过SurfaceHolder的unlockCanvasAndPost方法提交绘制结果并释放Canvas对象。
+
+
+
+
+#### 4.2 SurfaceHolder.Callback
+- SurfaceView必须实现SurfaceHolder的Callback接口
+    - 主要是3个方法,分别是surfaceCreated、surfaceChanged、surfaceDestroyed。从名字就可以看出来这个是监听SurfaceView状态的,跟Activity的生命周期有点像。
+- 接口中有以下三个方法作用说明
+    - public void surfaceCreated(SurfaceHolder holder):
+        - Surface 第一次创建时被调用,例如 SurfaceView 从不可见状态到可见状态 。在这个方法被调用到 surfaceDestroyed 方法被调用之前,Surface 对象可以被操作。也就是说,在界面可见的情况下,可以对 SurfaceView 进行绘制。
+        - surfaceCreated方法中一般做初始化动作,比如设置绘制线程的标记位,创建用于绘制的子线程等
+    - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height):
+        - Surface 大小和格式改变时会被调用,例如横竖屏切换时,如果需要对 Surface 的图像进行处理,就需要在这里实现。这个方法在 surfaceCreated 之后至少会被调用一次 。
+    - public void surfaceDestroyed(SurfaceHolder holder):
+        - Surface 被销毁时被调用,例如 SurfaceView 从可见到不可见状态时。 在这个方法被调用过之后,不能够再对 Surface 对象进行任何操作,所以绘图线程不能再对 SurfaceView 进行操作。
+        - surfaceDestroyed被调用后,就不能再对Surface对象进行任何操作,所以我们需要在surfaceDestroyed方法中将绘制的子线程停掉。
+
+
+#### 4.3 SurfaceHolder类源码
+- 首先看一下源码,这里省略了部分代码
+    - SurfaceHolder中的接口可以分为2类
+    - 一类是Callback接口,也就是我们上面模版代码中实现的3个接口方法,这类接口主要是用于监听SurfaceView的状态,以便我们进行相应的处理,比如创建绘制子线程,停止绘制等。
+    - 另一类方法主要用于和Surface以及SurfaceView交互,比如lockCanvas方法和unlockCanvasAndPost方法用于获取Canvas以及提交绘制结果等。
+    ```
+    public interface SurfaceHolder {
+    
+        public interface Callback {
+    
+            public void surfaceCreated(SurfaceHolder holder);
+    
+            public void surfaceChanged(SurfaceHolder holder, int format, int width,int height);
+    
+            public void surfaceDestroyed(SurfaceHolder holder);
+        }
+    
+        public interface Callback2 extends Callback {
+    
+            void surfaceRedrawNeeded(SurfaceHolder holder);
+    
+            default void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable drawingFinished) {
+                surfaceRedrawNeeded(holder);
+                drawingFinished.run();
+            }
+        }
+    
+        public void addCallback(Callback callback);
+    
+        public void removeCallback(Callback callback);
+    
+        public Canvas lockCanvas();
+    
+        public Canvas lockCanvas(Rect dirty);
+    
+        default Canvas lockHardwareCanvas() {
+            throw new IllegalStateException("This SurfaceHolder doesn't support lockHardwareCanvas");
+        }
+    
+        public void unlockCanvasAndPost(Canvas canvas);
+    
+        public Rect getSurfaceFrame();
+    
+        public Surface getSurface();
+    }
+    ```
+
+
+#### 4.4 SurfaceView部分源码
+- 代码如下所示,这里省略了部分代码
+    - SurfaceView继承自View,但是其实和View是有很大的不同的,除了前面介绍的几点SurfaceView的特性外,在底层SurfaceView也很大的不同,包括拥有自己独立的绘图表面等。
+    - 从下面SurfaceView的源码中我们可以看到,调用SurfaceHolder的lockCanvas方法实际上调用的是Surface的lockCanvas方法,返回的是Surface中的Canvas。并且调用过程加了一个可重入锁mSurfaceLock。所以绘制过程中只能绘制完一帧内容并提交更改以后才会释放Canvas,也就是才能继续下一帧的绘制操作。
+    ```
+    public class SurfaceView extends View {
+
+        final Surface mSurface = new Surface(); 
+        //锁
+        final ReentrantLock mSurfaceLock = new ReentrantLock();
+        
+        //当滑动的时候,也会不断更新surface
+        private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
+        = new ViewTreeObserver.OnScrollChangedListener() {
+                @Override
+                public void onScrollChanged() {
+                    updateSurface();
+                }
+        };
+        
+        //这个方法是更新surface
+        protected void updateSurface() {
+            if (!mHaveFrame) {
+                return;
+            }
+            if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
+                try {
+                    mSurfaceLock.lock();
+                    try {
+                        mDrawingStopped = !visible;
+                        SurfaceControl.openTransaction();
+                        try {
+                            mSurfaceControl.setLayer(mSubLayer);
+                            if (mViewVisibility) {
+                                mSurfaceControl.show();
+                            } else {
+                                mSurfaceControl.hide();
+                            }
+                            if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
+                                mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
+                                mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
+                                        0.0f, 0.0f,
+                                        mScreenRect.height() / (float) mSurfaceHeight);
+                            }
+                            if (sizeChanged) {
+                                mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
+                            }
+                        } finally {
+                            SurfaceControl.closeTransaction();
+                        }
+                    } finally {
+                        mSurfaceLock.unlock();
+                    }
+            } 
+        }
+        
+        private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+    
+            @Override
+            public void addCallback(Callback callback) {
+                synchronized (mCallbacks) {
+                    if (mCallbacks.contains(callback) == false) {
+                        mCallbacks.add(callback);
+                    }
+                }
+            }
+    
+            @Override
+            public void removeCallback(Callback callback) {
+                synchronized (mCallbacks) {
+                    mCallbacks.remove(callback);
+                }
+            }
+            
+            @Override
+            public Canvas lockCanvas() {
+                return internalLockCanvas(null);
+            }
+    
+            @Override
+            public Canvas lockCanvas(Rect inOutDirty) {
+                return internalLockCanvas(inOutDirty);
+            }
+    
+            private final Canvas internalLockCanvas(Rect dirty) {
+                mSurfaceLock.lock();
+    
+                Canvas c = null;
+                if (!mDrawingStopped && mWindow != null) {
+                    try {
+                        c = mSurface.lockCanvas(dirty);
+                    } catch (Exception e) {
+                        Log.e(LOG_TAG, "Exception locking surface", e);
+                    }
+                }
+    
+                if (c != null) {
+                    mLastLockTime = SystemClock.uptimeMillis();
+                    return c;
+                }
+    
+                long now = SystemClock.uptimeMillis();
+                long nextTime = mLastLockTime + 100;
+                if (nextTime > now) {
+                    try {
+                        Thread.sleep(nextTime-now);
+                    } catch (InterruptedException e) {
+                    }
+                    now = SystemClock.uptimeMillis();
+                }
+                mLastLockTime = now;
+                mSurfaceLock.unlock();
+    
+                return null;
+            }
+    
+            @Override
+            public void unlockCanvasAndPost(Canvas canvas) {
+                mSurface.unlockCanvasAndPost(canvas);
+                mSurfaceLock.unlock();
+            }
+    
+            @Override
+            public Surface getSurface() {
+                return mSurface;
+            }
+    
+            @Override
+            public Rect getSurfaceFrame() {
+                return mSurfaceFrame;
+            }
+        };
+    }
+    ```
+
+
+#### 4.5 看看Surface源码
+- 源码如下所示
+    - Surface实现了Parcelable接口,因为它需要在进程间以及本地方法间传输。Surface中创建了Canvas对象,用于执行具体的绘制操作。
+    ```
+    public class Surface implements Parcelable {
+    
+        final Object mLock = new Object(); // protects the native state
+        private final Canvas mCanvas = new CompatibleCanvas();
+    
+        public Canvas lockCanvas(Rect inOutDirty)
+                throws Surface.OutOfResourcesException, IllegalArgumentException {
+            synchronized (mLock) {
+                checkNotReleasedLocked();
+                if (mLockedObject != 0) {
+                    throw new IllegalArgumentException("Surface was already locked");
+                }
+                mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
+                return mCanvas;
+            }
+        }
+        
+        public void unlockCanvasAndPost(Canvas canvas) {
+            synchronized (mLock) {
+                checkNotReleasedLocked();
+    
+                if (mHwuiContext != null) {
+                    mHwuiContext.unlockAndPost(canvas);
+                } else {
+                    unlockSwCanvasAndPost(canvas);
+                }
+            }
+        }
+    
+        private void unlockSwCanvasAndPost(Canvas canvas) {
+            if (canvas != mCanvas) {
+                throw new IllegalArgumentException("canvas object must be the same instance that "
+                        + "was previously returned by lockCanvas");
+            }
+            if (mNativeObject != mLockedObject) {
+                Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
+                        Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
+                        Long.toHexString(mLockedObject) +")");
+            }
+            if (mLockedObject == 0) {
+                throw new IllegalStateException("Surface was not locked");
+            }
+            try {
+                nativeUnlockCanvasAndPost(mLockedObject, canvas);
+            } finally {
+                nativeRelease(mLockedObject);
+                mLockedObject = 0;
+            }
+        }
+    }
+    ```
+
+
+
+### 05.SurfaceView总结
+#### 5.1 使用场景
+- SurfaceView主要用于游戏、视频等复杂视觉效果的场景,利用双缓冲机制,在子线程中执行复杂的绘制操作,可以防止阻塞UI线程。
+- 我们在使用SurfaceView时一般都要实现Runnable接口和SurfaceHolder的Callback接口,并开启子线程进行具体的绘制操作
+
+
+
+
+#### 5.2 为何使用SurfaceView
+- 一是,如果屏幕刷新频繁,onDraw方法会被频繁的调用,onDraw方法执行的时间过长,会导致掉帧,出现页面卡顿。而SurfaceView采用了双缓冲技术,提高了绘制的速度,可以缓解这一现象。
+- 二是,view的onDraw方法是运行在主线程中的,会轻微阻塞主线程,对于需要频繁刷新页面的场景,而且onDraw方法中执行的操作比较耗时,会导致主线程阻塞,用户事件的响应受到影响,也就是响应速度下降,影响了用户的体验。而SurfaceView可以在自线程中更新UI,不会阻塞主线程,提高了响应速度。
+
+
+
+
+
+
+
+

+ 4 - 2
read/25.TextureView深入学习.md

@@ -18,8 +18,10 @@
 
 
 
 
 #### 1.2 如何实现视频播放功能
 #### 1.2 如何实现视频播放功能
-- SurfaceTexture的准备就绪、大小变化、销毁、更新等状态变化时都会回调相对应的方法。当TextureView内部创建好SurfaceTexture后,在监听器的onSurfaceTextureAvailable方法中,用SurfaceTexture来关联MediaPlayer,作为播放视频的图像数据来源。
-- SurfaceTexture作为数据通道,把从数据源(MediaPlayer)中获取到的图像帧数据转为GL外部纹理,交给TextureVeiw作为View heirachy中的一个硬件加速层来显示,从而实现视频播放功能。
+- SurfaceTexture的准备就绪、大小变化、销毁、更新等状态变化时都会回调相对应的方法。
+    - 当TextureView内部创建好SurfaceTexture后,在监听器的onSurfaceTextureAvailable方法中,用SurfaceTexture来关联MediaPlayer,作为播放视频的图像数据来源。
+- SurfaceTexture作为数据通道
+    - 把从数据源(MediaPlayer)中获取到的图像帧数据转为GL外部纹理,交给TextureVeiw作为View heirachy中的一个硬件加速层来显示,从而实现视频播放功能。
 
 
 
 
 
 

+ 94 - 0
read/26.视频编码和解码学习.md

@@ -1,5 +1,99 @@
 # 视频编码和解码
 # 视频编码和解码
 #### 目录介绍
 #### 目录介绍
+- **1.做Android视频编辑的可行性开源方案**
+- 1.1 第三方流行框架
+- 1.2 Android自带框架
+- 1.3 这些方案功能分析
+- 1.4 这些方案学习门槛分析
+- **2.对音频进行编码和解码**
+- 2.1 MediaCodec基本介绍
+- 2.2 MediaCodec核心原理
+- **3.如何通俗理解编码和解码**
+- 3.1 什么是编码
+- 3.2 什么是解码
+- **4.对音频如何混音添加背景音乐**
+- 4.1 待更新实现
+
+
+
+### 1.做Android视频编辑的可行性开源方案
+#### 1.1 第三方流行框架
+- 大家熟知的ffmpeg,将ffmpeg移植到anroid平台,编译成so文件,由jni 调用,可以实现音视频的分离、裁剪、拼合、加字幕、滤镜等功能。
+
+
+#### 1.2 Android自带框架
+- android 自带的MediaCodec 框架,MediaCodec框架底层调用的是StageFright库,StageFright库是默认封装在android系统里面的
+- 官方的 MediaCodec API 虽然支持硬件编解码加速,但是问题和局限还是很多的,一方面是只能在 Android 4.1 以上机型上才能使用,另一方面,由于 Android 手机种类繁多,厂商对底层源码的修改各不相同,导致 MediaCodec API 在实际使用中,会遇到很多坑,有很多兼容性的问题,因此,可以考虑采用第三方的编解码库。
+
+
+#### 1.3 这些方案功能分析
+- **1.3.1 ffmpeg功能分析**
+- ffmpeg 无疑排第一位,他集合了视频编解码、视频滤镜、流媒体推流、音频各种特效等等,基本上你能想到的功能都在里面。
+- 在网上看到说,国内像暴风,某某影音,某某视频等等都用到了该开源框架,
+
+- **1.3.1 MediaCodec功能分析**
+- MediaCodec涵盖了音视频解复用、音频解码、视频解码、音频编码、视频编码、音视频合并的整个流程。跟ffmpeg相比,MediaCodec 更接近底层硬件。这个方案如果想要实现视频的滤镜、字幕、拼接等功能的话,需要自己配合OpenGL ES 来实现,另外,音视频拼接的话,要考虑到不同音频采样率的重采样问题,音频重采用问题,需要懂得傅立叶变换相关的离散信号变换方法,如果要实现音频特效,如变声、均衡器的话,也需要懂得上述信号变换方法。
+
+
+#### 1.4 这些方案学习门槛分析
+- 如果只是做视频转码、加文字、图片特效等,ffmpeg和MediaCodec 旗鼓相当。如果是要拼接视频、做音频的变声、均衡器特效的话,MediaCodec是难度最高的,因为这一切需要你从底层原理做起。
+- 运行效率:MediaCodec硬解硬编最快,ffmpeg硬解硬编方案稍慢。
+- 稳定性: MediaCodec和ffmpeg 的硬解硬编方案旗鼓相当。
+- 打包占用空间:国内最得最好的ffmpeg硬解硬编方案,其so文件在10.几M,MediaCodec由于是纯java 代码,占用空间很容易做到几百K甚至几十K。
+
+
+### 2.对音频进行编码
+#### 2.1 MediaCodec基本介绍
+- 提供了一套访问 Android 底层多媒体模块的接口,主要是音视频的编解码接口
+- Android 应用层统一由 MediaCodec API 来提供各种音视频编解码功能,由参数配置来决定采用何种编解码算法、是否采用硬件编解码加速等等
+
+#### 2.2 MediaCodec核心原理
+- **2.2.1 MediaCodec 使用的基本流程**
+```
+- createEncoderByType/createDecoderByType
+- configure
+- start
+- while(1) {
+    - dequeueInputBuffer
+    - queueInputBuffer
+    - dequeueOutputBuffer
+    - releaseOutputBuffer
+}
+- stop
+- release
+```
+
+- **2.2.2 Buffer队列示意图**
+- ![image](http://s3.51cto.com/wyfs02/M00/7E/7B/wKioL1cCSojSz4HdAAD0xxQxvwg230.png)
+
+- **2.2.3 缓冲处理队列的逻辑**
+- MediaCodec 架构上采用了2个缓冲区队列,异步处理数据,下面描述的 Client 和 MediaCodec 模块是并行工作的(注:这里的 Client 就是指 “开发者,API 的使用者”):
+    * (1)Client 从 input 缓冲区队列申请 empty buffer [dequeueInputBuffer]
+    * (2)Client 把需要编解码的数据拷贝到 empty buffer,然后放入 input 缓冲区队列 [queueInputBuffer] 
+    * (3)MediaCodec 模块从 input 缓冲区队列取一帧数据进行编解码处理
+    * (4)编解码处理结束后,MediaCodec 将原始数据 buffer 置为 empty 后放回 input 缓冲区队列,将编解码后的数据放入到 output 缓冲区队列
+    * (5)Client 从 output 缓冲区队列申请编解码后的 buffer [dequeueOutputBuffer]
+    * (6)Client 对编解码后的 buffer 进行渲染/播放
+    * (7)渲染/播放完成后,Client 再将该 buffer 放回 output 缓冲区队列 [releaseOutputBuffer]
+- MediaCodec 在架构上,其实是采用了一种基于“环形缓冲区”的“生产者-消费者”模式,它设计了 2 个基于 idx 序号的“环形缓冲区” ,注意,是 2 个,一个在 input 端, 一个在 output 端。
+
+
+
+
+### 5.其他问题说明
+#### 5.1 版本更新情况
+- v1.0.0 2017年12月8日
+- v1.0.1 2018年2月2日
+
+
+#### 5.2 参考链接
+- Android硬编码——音频编码、视频编码及音视频混合:http://blog.csdn.net/junzia/article/details/54018671
+- android MediaCodec 音频编解码的实现——转码:https://www.cnblogs.com/Sharley/p/5964490.html
+- Android音频开发(5):音频数据的编解码:http://blog.51cto.com/ticktick/1760191
+- Android 音视频编辑经验总结及开源工程分享:https://www.cnblogs.com/jerrychen33/p/8148993.html
+
+
+
 
 
 
 
 
 

+ 2 - 2
read/29.视频播放器埋点监听.md

@@ -3,7 +3,7 @@
 - 01.传统一点的做法
 - 01.传统一点的做法
 - 02.如何实现统一埋点
 - 02.如何实现统一埋点
 - 03.具体实现代码案例
 - 03.具体实现代码案例
-
+- 04.如何配置使用
 
 
 
 
 ### 01.传统一点的做法
 ### 01.传统一点的做法
@@ -85,7 +85,7 @@
     ``` 
     ``` 
 
 
 
 
-
+### 04.如何配置使用
 
 
 
 
 
 

+ 171 - 0
read/30.音视频问题考点.md

@@ -0,0 +1,171 @@
+# 视频录制和编辑
+#### 目录介绍
+
+
+
+
+
+#### 14.0.0.0 SurfaceView是做什么?SurfaceView和View的本质区别?SurfaceView优缺点有哪些?
+- SurfaceView是用来做什么?
+    - SurfaceView 是 Android 中一种比较特殊的视图,它与视图容器并不是在同一个视图层上,绘制在一个独立的线程中完成,不需要及时响应用户的输入,也不会造成响应的 ANR 问题。SurfaceView 一般用在游戏、视频、摄影等一些复杂 UI 且高效的图像的显示,这类的图像处理都需要开单独的线程来处理。
+- SurfaceView和View的最本质的区别?
+    - SurfaceView是在一个新起的单独线程【子线程】中可以重新绘制画面,而view必须在UI的主线程中更新画面。
+    - 在UI的主线程中更新画面可能会引发问题,比如你更新的时间过长,那么你的主UI线程就会被你正在画的函数阻塞。那么将无法响应按键、触屏等消息;使用SurfaceView由于是在新的线程中更新画面,或者说SurfaceView却能在非UI线程中绘制,所以不会阻塞你的UI主线程导致卡顿甚至ANR。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要SurfaceView中thread处理,一般就需要有一个event queue的设计来保存touchevent,这会稍稍复杂一点,因为涉及到线程安全。
+    - View 在绘图时没有实现双缓冲机制,SurfaceView 在底层机制中就实现了双缓冲机制。
+- SurfaceView优缺点有哪些?
+    - 优点:
+        - 可以在一个独立的线程中进行绘制,不会影响主线程
+        - 使用双缓冲机制,播放视频时画面更流畅
+    - 缺点:
+        - Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中。SurfaceView 不能嵌套使用。【或者说不具备view的属性】
+
+
+
+#### 14.0.0.1 SurfaceView如何保证UI界面的流畅性?如何理解双缓冲机制?
+- SurfaceView如何保证UI界面的流畅性
+    - SurfaceView中采用了双缓冲机制,保证了UI界面的流畅性
+- 双缓冲机制由来
+    - 问题的由来
+        - CPU访问内存的速度要远远快于访问屏幕的速度。如果需要绘制大量复杂的图像时,每次都一个个从内存中读取图形然后绘制到屏幕就会造成多次地访问屏幕,从而导致效率很低。这就跟CPU和内存之间还需要有三级缓存一样,需要提高效率。
+    - 第一层缓冲
+        - 在绘制图像时不用上述一个一个绘制的方案,而采用先在内存中将所有的图像都绘制到一个Bitmap对象上,然后一次性将内存中的Bitmap绘制到屏幕,从而提高绘制的效率。Android中View的onDraw()方法已经实现了这一层缓冲。onDraw()方法中不是绘制一点显示一点,而是都绘制完后一次性显示到屏幕。
+    - 第二层缓冲
+        - onDraw()方法的Canvas对象是和屏幕关联的,而onDraw()方法是运行在UI线程中的,如果要绘制的图像过于复杂,则有可能导致应用程序卡顿,甚至ANR。因此我们可以先创建一个临时的Canvas对象,将图像都绘制到这个临时的Canvas对象中,绘制完成之后再将这个临时Canvas对象中的内容(也就是一个Bitmap),通过drawBitmap()方法绘制到onDraw()方法中的canvas对象中。这样的话就相当于是一个Bitmap的拷贝过程,比直接绘制效率要高,可以减少对UI线程的阻塞。
+- 双缓冲在运用时可以理解为:
+    - SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储的是上一次更改前的视图,当使用lockCanvas()获取画布时,得到的实际上是backCanvas而不是正在显示的frontCanvas,之后你在获取到的backCanvas上绘制新视图,再unlockCanvasAndPost(canvas)此视图,那么上传的这张canvas将替换原来的frontCanvas作为新的frontCanvas,原来的frontCanvas将切换到后台作为backCanvas。例如,如果你已经先后两次绘制了视图A和B,那么你再调用lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你将重绘的C视图上传,那么C将取代B作为新的frontCanvas显示在SurfaceView上,原来的B则转换为backCanvas。
+
+
+
+
+
+
+
+#### 14.0.0.2 SurfaceView在新的线程中更新画面为何不会阻塞UI主线程?是否跟在子线程中不能操作UI矛盾?
+- SurfaceView在新的线程中更新画面为何不会阻塞UI主线程?
+    - 首先看一下下面代码
+        - 实现 SurfaceHolder.Callback 接口,并且重写里面的三个方法
+        - 在 SurfaceHolder.Callback 的 surfaceCreated 方法中开启一个线程进行图像的绘制
+        - 在 SufaceHolder.Callback 的 surfaceDestroyed 方法中,结束绘制线程并调用 SurfaceHolder 的 removeCallbck 方法
+        - 在绘制线程每帧开始之前,调用 lockCanvas 方法锁住画布进行绘图
+        - 绘制完一帧的数据之后,调用 unlockCanvasAndPost 方法提交数据来显示图像
+        - 用于控制子线程绘制的标记参数,如上面代码中的mIsDrawing变量,需要用volatile关键字修饰,以保证多线程安全。
+        - 由于SurfaceView常被用于游戏、视频等场景,绘制操作会相对复杂很多,通常都需要开启子线程,在子线程中执行绘制操作,以免阻塞UI线程。在子线程中,我们通过SurfaceHolder的lockCanvas方法获取Canvas对象来进行具体的绘制操作,此时Canvas对象被当前线程锁定,绘制完成后通过SurfaceHolder的unlockCanvasAndPost方法提交绘制结果并释放Canvas对象。
+    - 如果上面不容易理解,那么这里提供伪代码,一看应该就更加容易明白!
+
+    ```
+    /**
+     * 必须实现SurfaceHolder.Callback接口和Runnable接口
+     */
+    public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
+    
+        // 是否绘制
+        private volatile boolean mIsDrawing;
+        // SurfaceView 控制器
+        private SurfaceHolder mSurfaceHolder;
+        // 画笔
+        private Paint mPaint;
+        // 画布
+        private Canvas mCanvas;
+        // 独立的线程
+        private Thread mThread;
+    
+        public MySurfaceView(Context context) {
+            super(context);
+            init();
+        }
+    
+        public MySurfaceView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            init();
+        }
+    
+        public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+            init();
+        }
+    
+        private void init() {
+            mSurfaceHolder = getHolder();
+            // 注册回调事件
+            mSurfaceHolder.addCallback(this);
+    
+            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mPaint.setStyle(Paint.Style.STROKE);
+        }
+    
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            VideoLogUtil.d("onSurfaceCreated");
+            mThread = new Thread(this, "yc");
+        }
+    
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            VideoLogUtil.d("onSurfaceChanged"+format+ "----"+ width+ "----"+ height);
+            //并开启线程
+            mIsDrawing = true;
+            mThread.start();
+        }
+    
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            VideoLogUtil.d("onSurfaceDestroyed");
+            // 不再绘制,移除回调,线程终止
+            mIsDrawing = false;
+            mSurfaceHolder.removeCallback(this);
+            mThread.interrupt();
+        }
+    
+        @Override
+        public void run() {
+            while (mIsDrawing) {
+                VideoLogUtil.d("draw canvas");
+                // 锁定画布,获得画布对象
+                mCanvas = mSurfaceHolder.lockCanvas();
+                try {
+                    //使用画布做具体的绘制
+                    draw();
+                    // 线程休眠 100 ms
+                    Thread.sleep(100);
+                } catch (Exception e) {
+                    VideoLogUtil.d(e.getMessage());
+                } finally {
+                    // 解锁画布,提交绘制,显示内容
+                    if (mCanvas != null) {
+                        mSurfaceHolder.unlockCanvasAndPost(mCanvas);
+                    }
+                }
+            }
+        }
+    
+        private void draw() {
+            //开始绘制
+        }
+    }
+    ```
+- 是否跟在子线程中不能操作UI矛盾?
+    - 为什么不允许在子线程中访问UI《Android艺术探索》
+        - 这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那么为什么系统不对UI控件的访问加上锁机制呢?缺点有两个:
+        - ①首先加上锁机制会让UI访问的逻辑变得复杂
+        - ②锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
+        - 所以最简单且高效的方法就是采用单线程模型来处理UI操作。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 808 - 0
read/34.音频播放锁屏分析.md

@@ -0,0 +1,808 @@
+# 视频加密和解密
+#### 目录介绍
+- **1.类似酷狗等锁屏页面实现步骤**
+- 1.1 什么是锁屏联动媒体播放器
+- 1.2 如何实现锁屏页面
+- 1.3 关于自定义锁屏页面左右滑动的控件
+- 1.4 注意要点分析
+- 1.5 具体完整代码的案例
+- 1.6 效果图展示案例
+- **2.自定义锁屏页的基本原理**
+- 2.1 基本原理
+- 2.2 原理图形展示
+- 2.3 讨论一些细节
+- **3.锁屏Activity配置信息说明**
+- 3.1 去掉系统锁屏做法
+- 3.2 权限问题
+- **4.屏蔽物理或者手机返回键**
+- 4.1 为什么要这样处理
+- 4.2 如何实现,实现逻辑代码
+- **5.滑动屏幕解锁**
+- 5.1 滑动解锁原理
+- 5.2 滑动控件自定义
+- **6.透明栏与沉浸模式**
+- 6.1 透明栏与沉浸模式的概念
+- 6.2 如何实现,代码展示
+- **7.用户指纹识别,如何锁屏页面失效**
+- **8.关于其他**
+- 8.1 版本更新情况
+- 8.2 参考案例
+- 8.3 个人博客
+
+
+###  0.备注
+- 建议结合代码,看博客更加高效,项目地址:https://github.com/yangchong211/
+- [博客大汇总,持续更新目录说明,记录所有开源项目和博客](http://www.jianshu.com/p/53017c3fc75d)
+
+
+### 1.类似酷狗等锁屏页面实现步骤
+#### 1.1 什么是锁屏联动媒体播放器
+- **播放器除了播放了音乐之外什么都没做,就可以分别在任务管理、锁屏、负一屏控制播放器。**
+- 也可以这样通俗的解释,这个举例子说一个应用场景,我使用混沌大学听音频,然后我关闭了屏幕(屏幕灭了),当我再次打开的时候,屏幕的锁屏页面或者顶层页面便会出现一层音频播放器控制的页面,那么即使我不用解锁屏幕,也照样可以控制音频播放器的基本播放操作。如果你细心观察一下,也会发现有些APP正式这样操作的。目前我发现QQ音乐,酷狗音乐,混沌大学等是这样的
+- **如何实现,逻辑思路**
+- 第一步:在服务中注册屏幕熄灭广播
+- 第二步:处理逻辑,发现屏幕熄灭就开启锁屏页面,再次点亮屏幕时就可以看到锁屏页面
+- 第三步:点击锁屏页面上的按钮,比如上一首,下一首,播放暂停可以与主程序同步信息。
+- 第四步:滑动锁屏页面,锁屏页面被销毁,进入程序主界面。
+
+
+#### 1.2 如何实现锁屏页面
+- **1.2.1 注册一个广播接收者监听屏幕亮了或者灭了**
+```
+public class AudioBroadcastReceiver extends BroadcastReceiver {
+    
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if(action!=null && action.length()>0){
+            switch (action){
+                //锁屏时处理的逻辑
+                case Constant.LOCK_SCREEN:
+                    PlayService.startCommand(context,Constant.LOCK_SCREEN);
+                    break;
+                //当屏幕灭了
+                case Intent.ACTION_SCREEN_OFF:
+                    PlayService.startCommand(context,Intent.ACTION_SCREEN_OFF);
+                    break;
+                //当屏幕亮了
+                case Intent.ACTION_SCREEN_ON:
+                    PlayService.startCommand(context,Intent.ACTION_SCREEN_ON);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
+```
+- **1.2.2 在服务中开启和注销锁屏操作**
+- 在oncreate方法中注册广播接收者
+```
+final IntentFilter filter = new IntentFilter();
+//锁屏
+filter.addAction(Constant.LOCK_SCREEN);
+//当屏幕灭了
+filter.addAction(Intent.ACTION_SCREEN_OFF);
+//当屏幕亮了
+filter.addAction(Intent.ACTION_SCREEN_ON);
+registerReceiver(mAudioReceiver, filter);
+```
+
+- 在ondestory方法中注销广播接收者
+```
+unregisterReceiver(mAudioReceiver);
+```
+
+
+#### 1.3 关于自定义锁屏页面左右滑动的控件
+- **1.3.1 只有从左向右滑动的自定义控件**
+
+```
+public class SlitherFinishLayout extends RelativeLayout implements OnTouchListener {
+
+    /** 
+     * SlitherFinishLayout布局的父布局 
+     */  
+    private ViewGroup mParentView;
+    /** 
+     * 处理滑动逻辑的View 
+     */  
+    private View touchView;
+    /** 
+     * 滑动的最小距离 
+     */  
+    private int mTouchSlop;  
+    /** 
+     * 按下点的X坐标 
+     */  
+    private int downX;  
+    /** 
+     * 按下点的Y坐标 
+     */  
+    private int downY;  
+    /** 
+     * 临时存储X坐标 
+     */  
+    private int tempX;  
+    /** 
+     * 滑动类 
+     */  
+    private Scroller mScroller;
+    /** 
+     * SlitherFinishLayout的宽度 
+     */  
+    private int viewWidth;  
+    /** 
+     * 记录是否正在滑动 
+     */  
+    private boolean isSlither;  
+      
+    private OnSlitherFinishListener onSlitherFinishListener;  
+    private boolean isFinish;  
+      
+  
+    public SlitherFinishLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);  
+    }  
+
+
+    public SlitherFinishLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mScroller = new Scroller(context);
+    }  
+
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {  
+        super.onLayout(changed, l, t, r, b);  
+        if (changed) {  
+            // 获取SlitherFinishLayout所在布局的父布局  
+            mParentView = (ViewGroup) this.getParent();
+            viewWidth = this.getWidth();  
+        }  
+    }  
+
+
+    /** 
+     * 设置OnSlitherFinishListener, 在onSlitherFinish()方法中finish Activity 
+     * @param onSlitherFinishListener           listener
+     */  
+    public void setOnSlitherFinishListener(OnSlitherFinishListener onSlitherFinishListener) {
+        this.onSlitherFinishListener = onSlitherFinishListener;  
+    }  
+  
+    /** 
+     * 设置Touch的View 
+     * @param touchView
+     */  
+    public void setTouchView(View touchView) {
+        this.touchView = touchView;  
+        touchView.setOnTouchListener(this);  
+    }  
+
+
+    public View getTouchView() {
+        return touchView;  
+    }
+
+  
+    /** 
+     * 滚动出界面 
+     */  
+    private void scrollRight() {  
+        final int delta = (viewWidth + mParentView.getScrollX());  
+        // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item  
+        mScroller.startScroll(mParentView.getScrollX(), 0, -delta + 1, 0, Math.abs(delta));
+        postInvalidate();  
+    }  
+  
+    /** 
+     * 滚动到起始位置 
+     */  
+    private void scrollOrigin() {  
+        int delta = mParentView.getScrollX();  
+        mScroller.startScroll(mParentView.getScrollX(), 0, -delta, 0, Math.abs(delta));
+        postInvalidate();  
+    }  
+  
+    /** 
+     * touch的View是否是AbsListView, 例如ListView, GridView等其子类 
+     * @return
+     */  
+    private boolean isTouchOnAbsListView() {
+        return touchView instanceof AbsListView ? true : false;
+    }  
+  
+    /** 
+     * touch的view是否是ScrollView或者其子类 
+     * @return
+     */  
+    private boolean isTouchOnScrollView() {  
+        return touchView instanceof ScrollView ? true : false;
+    }  
+
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        switch (event.getAction()) {  
+            case MotionEvent.ACTION_DOWN:
+                downX = tempX = (int) event.getRawX();
+                downY = (int) event.getRawY();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                int moveX = (int) event.getRawX();
+                int deltaX = tempX - moveX;
+                tempX = moveX;
+                if (Math.abs(moveX - downX) > mTouchSlop && Math.abs((int) event.getRawY() - downY) < mTouchSlop) {
+                    isSlither = true;
+                    // 若touchView是AbsListView,
+                    // 则当手指滑动,取消item的点击事件,不然我们滑动也伴随着item点击事件的发生
+                    if (isTouchOnAbsListView()) {
+                        MotionEvent cancelEvent = MotionEvent.obtain(event);
+                        cancelEvent.setAction(MotionEvent.ACTION_CANCEL
+                                        | (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
+                        v.onTouchEvent(cancelEvent);
+                    }
+                }
+                if (moveX - downX >= 0 && isSlither) {
+                    mParentView.scrollBy(deltaX, 0);
+                    // 屏蔽在滑动过程中ListView ScrollView等自己的滑动事件
+                    if (isTouchOnScrollView() || isTouchOnAbsListView()) {
+                        return true;
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                isSlither = false;
+                if (mParentView.getScrollX() <= -viewWidth / 2) {
+                    isFinish = true;
+                    scrollRight();
+                } else {
+                    scrollOrigin();
+                    isFinish = false;
+                }
+                break;
+            default:
+                break;
+        }
+        // 假如touch的view是AbsListView或者ScrollView 我们处理完上面自己的逻辑之后  
+        // 再交给AbsListView, ScrollView自己处理其自己的逻辑  
+        if (isTouchOnScrollView() || isTouchOnAbsListView()) {  
+            return v.onTouchEvent(event);  
+        }
+        // 其他的情况直接返回true  
+        return true;  
+    }  
+
+
+    @Override
+    public void computeScroll() {  
+        // 调用startScroll的时候scroller.computeScrollOffset()返回true,  
+        if (mScroller.computeScrollOffset()) {  
+            mParentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
+            postInvalidate();
+            if (mScroller.isFinished()) {
+                if (onSlitherFinishListener != null && isFinish) {  
+                    onSlitherFinishListener.onSlitherFinish();  
+                }  
+            }  
+        }  
+    }  
+      
+  
+    public interface OnSlitherFinishListener {  
+        void onSlitherFinish();
+    }  
+  
+}  
+```
+
+
+
+- **1.3.2 支持向左或者向右滑动的控件,灵活处理**
+
+```
+public class SlideFinishLayout extends RelativeLayout {
+
+    private final String TAG = SlideFinishLayout.class.getName();
+
+    /**
+     * SlideFinishLayout布局的父布局
+     */
+    private ViewGroup mParentView;
+
+    /**
+     * 滑动的最小距离
+     */
+    private int mTouchSlop;
+    /**
+     * 按下点的X坐标
+     */
+    private int downX;
+    /**
+     * 按下点的Y坐标
+     */
+    private int downY;
+    /**
+     * 临时存储X坐标
+     */
+    private int tempX;
+    /**
+     * 滑动类
+     */
+    private Scroller mScroller;
+    /**
+     * SlideFinishLayout的宽度
+     */
+    private int viewWidth;
+    /**
+     * 记录是否正在滑动
+     */
+    private boolean isSlide;
+
+    private OnSlideFinishListener onSlideFinishListener;
+
+    /**
+     * 是否开启左侧切换事件
+     */
+    private boolean enableLeftSlideEvent = true;
+    /**
+     * 是否开启右侧切换事件
+     */
+    private boolean enableRightSlideEvent = true;
+    /**
+     * 按下时范围(处于这个范围内就启用切换事件,目的是使当用户从左右边界点击时才响应)
+     */
+    private int size ;
+    /**
+     * 是否拦截触摸事件
+     */
+    private boolean isIntercept = false;
+    /**
+     * 是否可切换
+     */
+    private boolean canSwitch;
+    /**
+     * 左侧切换
+     */
+    private boolean isSwitchFromLeft = false;
+    /**
+     * 右侧侧切换
+     */
+    private boolean isSwitchFromRight = false;
+
+
+    public SlideFinishLayout(Context context) {
+        super(context);
+        init(context);
+    }
+    public SlideFinishLayout(Context context, AttributeSet attrs) {
+        super(context, attrs, 0);
+        init(context);
+    }
+    public SlideFinishLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    private void init(Context context) {
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        Log.i(TAG, "设备的最小滑动距离:" + mTouchSlop);
+        mScroller = new Scroller(context);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        if (changed) {
+            // 获取SlideFinishLayout所在布局的父布局
+            mParentView = (ViewGroup) this.getParent();
+            viewWidth = this.getWidth();
+            size = viewWidth;
+        }
+        Log.i(TAG, "viewWidth=" + viewWidth);
+    }
+
+
+    public void setEnableLeftSlideEvent(boolean enableLeftSlideEvent) {
+        this.enableLeftSlideEvent = enableLeftSlideEvent;
+    }
+
+
+    public void setEnableRightSlideEvent(boolean enableRightSlideEvent) {
+        this.enableRightSlideEvent = enableRightSlideEvent;
+    }
+
+    /**
+     * 设置OnSlideFinishListener, 在onSlideFinish()方法中finish Activity
+     * @param onSlideFinishListener         onSlideFinishListener
+     */
+    public void setOnSlideFinishListener(OnSlideFinishListener onSlideFinishListener) {
+        this.onSlideFinishListener = onSlideFinishListener;
+    }
+
+    /**
+     * 是否拦截事件,如果不拦截事件,对于有滚动的控件的界面将出现问题(相冲突)
+     */
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        float downX = ev.getRawX();
+        Log.i(TAG, "downX =" + downX + ",viewWidth=" + viewWidth);
+        if(enableLeftSlideEvent && downX < size){
+            Log.e(TAG, "downX 在左侧范围内 ,拦截事件");
+            isIntercept = true;
+            isSwitchFromLeft = true;
+            isSwitchFromRight = false;
+            return false;
+        }else if(enableRightSlideEvent && downX > (viewWidth - size)){
+            Log.i(TAG, "downX 在右侧范围内 ,拦截事件");
+            isIntercept = true;
+            isSwitchFromRight = true;
+            isSwitchFromLeft = false;
+            return true;
+        }else{
+            Log.i(TAG, "downX 不在范围内 ,不拦截事件");
+            isIntercept = false;
+            isSwitchFromLeft = false;
+            isSwitchFromRight = false;
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+
+    @SuppressLint("ClickableViewAccessibility")
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        //不拦截事件时 不处理
+        if(!isIntercept){
+            Log.d(TAG,"false------------");
+            return false;
+        }
+        Log.d(TAG,"true-----------");
+        switch (event.getAction()){
+            case MotionEvent.ACTION_DOWN:
+                downX = tempX = (int) event.getRawX();
+                downY = (int) event.getRawY();
+                Log.d(TAG,"downX---"+downX+"downY---"+downY);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                int moveX = (int) event.getRawX();
+                int deltaX = tempX - moveX;
+                tempX = moveX;
+                if (Math.abs(moveX - downX) > mTouchSlop && Math.abs((int) event.getRawY() - downY) < mTouchSlop) {
+                    isSlide = true;
+                }
+                Log.e(TAG, "scroll deltaX=" + deltaX);
+                //左侧滑动
+                if(enableLeftSlideEvent){
+                    if (moveX - downX >= 0 && isSlide) {
+                        mParentView.scrollBy(deltaX, 0);
+                    }
+                }
+                //右侧滑动
+                if(enableRightSlideEvent){
+                    if (moveX - downX <= 0 && isSlide) {
+                        mParentView.scrollBy(deltaX, 0);
+                    }
+                }
+                Log.i(TAG + "/onTouchEvent", "mParentView.getScrollX()=" + mParentView.getScrollX());
+                break;
+            case MotionEvent.ACTION_UP:
+                isSlide = false;
+                //mParentView.getScrollX() <= -viewWidth / 2  ==>指左侧滑动
+                //mParentView.getScrollX() >= viewWidth / 2   ==>指右侧滑动
+                if (mParentView.getScrollX() <= -viewWidth / 2 || mParentView.getScrollX() >= viewWidth / 2) {
+                    canSwitch = true;
+                    if(isSwitchFromLeft){
+                        scrollToRight();
+                    }
+
+                    if(isSwitchFromRight){
+                        scrollToLeft();
+                    }
+                } else {
+                    scrollOrigin();
+                    canSwitch = false;
+                }
+                break;
+            default:
+                break;
+        }
+        return true;
+    }
+
+
+    /**
+     * 滚动出界面至右侧
+     */
+    private void scrollToRight() {
+        final int delta = (viewWidth + mParentView.getScrollX());
+        // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
+        mScroller.startScroll(mParentView.getScrollX(), 0, -delta + 1, 0, Math.abs(delta));
+        postInvalidate();
+    }
+
+    /**
+     * 滚动出界面至左侧
+     */
+    private void scrollToLeft() {
+        final int delta = (viewWidth - mParentView.getScrollX());
+        // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
+        //此处就不可用+1,也不卡直接用delta
+        mScroller.startScroll(mParentView.getScrollX(), 0, delta - 1, 0, Math.abs(delta));
+        postInvalidate();
+    }
+
+    /**
+     * 滚动到起始位置
+     */
+    private void scrollOrigin() {
+        int delta = mParentView.getScrollX();
+        mScroller.startScroll(mParentView.getScrollX(), 0, -delta, 0, Math.abs(delta));
+        postInvalidate();
+    }
+
+    @Override
+    public void computeScroll(){
+        // 调用startScroll的时候scroller.computeScrollOffset()返回true,
+        if (mScroller.computeScrollOffset()) {
+            mParentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
+            postInvalidate();
+
+            if (mScroller.isFinished()) {
+                if (onSlideFinishListener != null && canSwitch) {
+                    //回调,左侧切换事件
+                    if(isSwitchFromLeft){
+                        onSlideFinishListener.onSlideBack();
+                    }
+                    //右侧切换事件
+                    if(isSwitchFromRight){
+                        onSlideFinishListener.onSlideForward();
+                    }
+                }
+            }
+        }
+    }
+
+
+    public interface OnSlideFinishListener {
+        void onSlideBack();
+        void onSlideForward();
+    }
+
+}
+```
+
+#### 1.4 注意要点分析
+- 1.4.1 在清单文件需要注册属性
+```
+ <activity android:name=".ui.lock.LockTestActivity"
+            android:noHistory="false"
+            android:excludeFromRecents="true"
+            android:screenOrientation="portrait"
+            android:exported="false"
+            android:launchMode="singleInstance"
+            android:theme="@style/LockScreenTheme"/>
+```
+
+- 1.4.2 程序在前台时,当从锁屏页面finish时,会有闪屏效果
+- 如果加上这句话android:launchMode="singleInstance",那么程序在前台时会有闪屏效果,如果在后台时,则直接展现栈顶页面
+- 如果不加这句话
+
+
+#### 1.5 具体完整代码的案例
+- 可以参考具体的案例代码:https://github.com/yangchong211/YCAudioPlayer
+- 可以参考的博客:https://www.jianshu.com/p/53017c3fc75d
+
+
+### 2.自定义锁屏页的基本原理
+#### 2.1 基本原理
+- Android系统实现自定义锁屏页的思路很简单,即在App启动时开启一个service,在Service中时刻监听系统SCREEN_OFF的广播,当屏幕熄灭时,Service监听到广播,开启一个锁屏页Activity在屏幕最上层显示,该Activity创建的同时会去掉系统锁屏(当然如果有密码是禁不掉的)。
+
+#### 2.2 原理图形展示
+- ![image](http://oa5504rxk.bkt.clouddn.com/week3_2/1.jpg)
+
+
+#### 2.3 讨论一些细节
+- **2.3.1 关于启动Activity时Intent的Flag问题**
+- 如果不添加FLAG_ACTIVITY_NEW_TASK的标志位,会出现“Calling startActivity() from outside of an Activity”的运行时异常,毕竟我们是从Service启动的Activity。Activity要存在于activity的栈中,而Service在启动activity时必然不存在一个activity的栈,所以要新起一个栈,并装入启动的activity。使用该标志位时,也需要在AndroidManifest中声明taskAffinity,即新task的名称,否则锁屏Activity实质上还是在建立在原来App的task栈中。
+- 标志位FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,是为了避免在最近使用程序列表出现Service所启动的Activity,但这个标志位不是必须的,其使用依情况而定。
+
+- **2.3.2 动态注册广播接收者**
+```
+IntentFilter mScreenOffFilter = new IntentFilter();
+mScreenOffFilter.addAction(Intent.ACTION_SCREEN_OFF);
+registerReceiver(mScreenOffReceiver, mScreenOffFilter);
+```
+
+
+### 3.锁屏Activity配置信息说明
+#### 3.1 去掉系统锁屏做法
+- 在自定义锁屏Activity的onCreate()方法里设定以下标志位就能完全实现相同的功能:
+```
+//注意需要做一下判断
+if (getWindow() != null) {
+    Window window = getWindow();
+    window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
+	    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
+	    WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+    // 锁屏的activity内部也要做相应的配置,让activity在锁屏时也能够显示,同时去掉系统锁屏。
+    // 当然如果设置了系统锁屏密码,系统锁屏是没有办法去掉的
+    // FLAG_DISMISS_KEYGUARD用于去掉系统锁屏页
+    // FLAG_SHOW_WHEN_LOCKED使Activity在锁屏时仍然能够显示
+    window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
+	    WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+    window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+	window.getDecorView().setSystemUiVisibility(
+		View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+			| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+			| View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE);
+    }
+}
+```
+
+
+#### 3.2 权限问题
+- 不要忘记在Manifest中加入适当的权限:
+```
+<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>  
+```
+
+### 4.屏蔽物理或者手机返回键
+#### 4.1 为什么要这样处理
+- 当自定义锁屏页最终出现在手机上时,我们总希望它像系统锁屏页那样屹立不倒,所有的按键都不能触动它,只有通过划瓶或者指纹才能解锁,因此有必要对按键进行一定程度上的屏蔽。针对只有虚拟按键的手机,我们可以通过隐藏虚拟按键的方式部分解决这个问题,具体方法在后文会介绍。但是当用户在锁屏页底部滑动,隐藏后的虚拟按键还是会滑出,而且如果用户是物理按键的话就必须进行屏蔽了。
+
+
+#### 4.2 如何实现,实现逻辑代码
+```
+@Override
+public void onBackPressed() {
+	// 不做任何事,为了屏蔽back键
+}
+
+@Override
+public boolean onKeyDown(int keyCode, KeyEvent event) {
+	int key = event.getKeyCode();
+	switch (key) {
+		case KeyEvent.KEYCODE_BACK: {
+			return true;
+		}
+		case KeyEvent.KEYCODE_MENU:{
+			return true;
+		}
+		default:
+			break;
+	}
+	return super.onKeyDown(keyCode, event);
+}
+```
+
+
+### 5.滑动屏幕解锁
+#### 5.1 滑动解锁原理
+- 当手指在屏幕上滑动时,拦截并处理滑动事件,使锁屏页面随着手指运动,当运动到达一定的阀值时,用户手指松开手指,锁屏页自动滑动到屏幕边界消失,如果没有达到运动阀值,就会自动滑动到起始位置,重新覆盖屏幕。
+- 对滑动的距离与阀值进行一个比较,此处的阀值为0.5*屏幕宽度,如果低于阀值,则移动到初始位置;如果高于阀值,以同样的方式移出屏幕右边界,然后将Activity干掉
+
+
+#### 5.2 滑动控件自定义
+- 具体可以看代码:项目地址https://github.com/yangchong211/YCAudioPlayer
+- 可以直接看weight——other——SlideFinishLayout/SlitherFinishLayout类
+
+
+
+### 6.透明栏与沉浸模式
+#### 6.1 透明栏与沉浸模式的概念
+- 沉浸模式与透明栏是两个不同的概念,由于某些原因,国内一些开发或产品会把这两个概念混淆。
+- **6.1.1 沉浸模式 什么是沉浸模式?**
+- 从4.4开始,Android 为 “setSystemUiVisibility()”方法提供了新的标记 “SYSTEM_UI_FLAG_IMMERSIVE”以及”SYSTEM_UI_FLAG_IMMERSIVE_STIKY”,就是我们所谈的沉浸模式,全称为 “Immersive Full-Screen Mode”,它可以使你的app隐藏状态栏和导航栏,实现真正意义上的全屏体验。
+- 之前 Android 也是有全屏模式的,主要通过”setSystemUiVisibility()”添加两个Flag,即”SYSTEM_UI_FLAG_FULLSCREEN”,”SYSTEM_UI_FLAG_HIDE_NAVIGATION”(仅适用于使用导航栏的设备,即虚拟按键)。
+- 这两个标记都存在一些问题,例如使用第一个标记的时候,除非 App 提供暂时退出全屏模式的功能(例如部分电子书软件中点击一次屏幕中央位置),用户是一直都没法看见状态栏的。这样,如果用户想去看看通知中心有什么通知,那就必须点击一次屏幕,显示状态栏,然后才能调出通知中心。
+- 而第二个标记的问题在于,Google 认为导航栏对于用户来说是十分重要的,所以只会短暂隐藏导航栏。一旦用户做其他操作,例如点击一次屏幕,导航栏就会马上被重新调出。这样的设定对于看图软件,视频软件等等没什么大问题,但是对于游戏之类用户需要经常点击屏幕的 App,那就几乎是悲剧了——这也是为什么你在 Android 4.4 之前找不到什么全屏模式会自动隐藏导航栏的应用。
+
+
+#### 6.2 如何实现,代码展示
+- **6.2.1 在案例中代码展示**
+```
+if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+	window.getDecorView().setSystemUiVisibility(
+			// SYSTEM_UI_FLAG_LAYOUT_STABLE保持整个View稳定,使View不会因为SystemUI的变化而做layout
+			View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+			// SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,开发者容易被其中的HIDE_NAVIGATION所迷惑,
+			// 其实这个Flag没有隐藏导航栏的功能,只是控制导航栏浮在屏幕上层,不占据屏幕布局空间;
+			View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+			View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+			// SYSTEM_UI_FLAG_HIDE_NAVIGATION,才是能够隐藏导航栏的Flag;
+			View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+			// SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,由上面可知,也不能隐藏状态栏,只是使状态栏浮在屏幕上层。
+			View.SYSTEM_UI_FLAG_FULLSCREEN |
+			View.SYSTEM_UI_FLAG_IMMERSIVE);
+}
+```
+
+- 注意的是,这段代码除了需要加在Activity的OnCreate()方法中,也要加在重写的onWindowFocusChanged()方法中,在窗口获取焦点时再将Flag设置一遍,否则在部分手机上可能导致无法达到预想的效果。一般情况下没有问题,最后建议还是加上
+```
+@Override
+public void onWindowFocusChanged(boolean hasFocus) {
+	super.onWindowFocusChanged(hasFocus);
+	if(hasFocus && getWindow()!=null){
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+			getWindow().getDecorView().setSystemUiVisibility(
+					// SYSTEM_UI_FLAG_LAYOUT_STABLE保持整个View稳定,使View不会因为SystemUI的变化而做layout
+					View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+							// SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,开发者容易被其中的HIDE_NAVIGATION所迷惑,
+							// 其实这个Flag没有隐藏导航栏的功能,只是控制导航栏浮在屏幕上层,不占据屏幕布局空间;
+							View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+							View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+							// SYSTEM_UI_FLAG_HIDE_NAVIGATION,才是能够隐藏导航栏的Flag;
+							View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+							// SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,由上面可知,也不能隐藏状态栏,只是使状态栏浮在屏幕上层。
+							View.SYSTEM_UI_FLAG_FULLSCREEN |
+							View.SYSTEM_UI_FLAG_IMMERSIVE);
+		}
+	}
+}
+```
+
+### 8.关于其他
+#### 8.1 版本更新情况
+#### 8.2 参考案例
+- Android添加锁屏界面:http://blog.csdn.net/sanjay_f/article/details/48653135
+- Android 自定义锁屏:http://blog.csdn.net/macaopark/article/details/73477986
+- Android锁屏实现与总结:https://www.jianshu.com/p/6c3a6b0f145e
+- 浅谈Android自定义锁屏页:https://www.2cto.com/kf/201607/528329.html
+- AndroidQQ音酷狗音乐锁屏控制实现原理,酷狗锁屏:http://blog.csdn.net/yangxi_pekin/article/details/50456763
+- Android仿网易云音乐中锁屏后在开锁界面插屏功能:http://blog.csdn.net/u010696525/article/details/51445515
+- android锁屏页面的实现:http://blog.csdn.net/mulanlong/article/details/52725050
+- 浅谈 Android自定义锁屏页的发车姿势:https://www.cnblogs.com/qianyukun/p/5855880.html
+
+
+#### 8.3 个人博客
+- **github:** [https://github.com/yangchong211](https://github.com/yangchong211)
+- **知乎:** [https://www.zhihu.com/people/yang-chong-69-24/pins/posts](https://www.zhihu.com/people/yang-chong-69-24/pins/posts)
+- **简书:** [http://www.jianshu.com/u/b7b2c6ed9284](http://www.jianshu.com/u/b7b2c6ed9284)
+- **csdn:** [http://my.csdn.net/m0_37700275](http://my.csdn.net/m0_37700275)
+- **喜马拉雅听书:** [http://www.ximalaya.com/zhubo/71989305/](http://www.ximalaya.com/zhubo/71989305/)
+- 泡在网上的日子:[http://www.jcodecraeer.com/member/content_list.php?channelid=1](http://www.jcodecraeer.com/member/content_list.php?channelid=1)
+- 邮箱:yangchong211@163.com
+- 阿里云博客:[https://yq.aliyun.com/users/article?spm=5176.100239.headeruserinfo.3.dT4bcV](https://yq.aliyun.com/users/article?spm=5176.100239.headeruserinfo.3.dT4bcV)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 184 - 0
read/35.耳机控制音视频音量.md

@@ -0,0 +1,184 @@
+#### **目录介绍**
+- **1.耳机拔出时暂停播放**
+- 1.1 拔出耳机自动暂停 , 插入耳机自动恢复播放
+- 1.2 实现的原理分析
+- 1.3 代码实现逻辑
+- **2.耳机线控,耳机控制声音**
+- 2.1 耳机按键也可以控制音量调节
+- 2.2 在5.0之前和5.0之后比较
+- 2.3 实现的逻辑分析
+- **3.蓝牙耳机特殊处理**
+- 3.1 如何监听蓝牙耳机打开关闭
+- 3.2 如何监听蓝牙耳机按键调节声控功能
+- **4.外放,耳机,听筒之间的切换**
+- 4.1 相关概念讲解
+- 4.2 代码展示案例
+- **5.其他问题说明**
+- 5.1 版本更新情况
+- 5.2 参考链接
+- 5.2 个人博客
+
+
+
+###  0.备注
+- 建议结合代码,看博客更加高效,项目地址:https://github.com/yangchong211/
+- [博客大汇总,持续更新目录说明,记录所有开源项目和博客](http://www.jianshu.com/p/53017c3fc75d)
+- 关于本项目地址:https://github.com/yangchong211/YCAudioPlayer
+- https://github.com/yangchong211/YCVideoPlayer
+
+
+### 1 耳机拔出时暂停播放
+#### 1.1 拔出耳机自动暂停 , 插入耳机自动恢复播放
+- 在使用音频APP时,细心的你有没有发现,拔出耳机,暂停播放了;插上耳机又恢复播放了。是不是很神奇……
+- 如何实现这个功能了,这个我也是通过百度才知道了,但是代码还是不太懂,IntentFilter作用?后来明白,其实不用深入底层原理也没有多大关系……
+
+#### 1.2 实现的原理分析
+- 其原理还是通过发广播接收者控制播放与暂停功能。首先创建一个广播接收者,然后在播放时注册,在暂停时取消,就可以实现这个功能。
+- 具体的逻辑可以直接参考代码……其实这个也是百度查的!
+
+#### 1.3 代码实现逻辑
+- **1.3.1 注册广播接收者**
+
+```
+/**
+ * 来电/耳机拔出时暂停播放
+ * 在播放时调用,在暂停时注销
+ */
+private final AudioEarPhoneReceiver mNoisyReceiver = new AudioEarPhoneReceiver();
+private final IntentFilter mFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+```
+
+- **1.3.2 在开始播放时注册,在暂停播放时注销**
+
+```
+//注册监听来电/耳机拔出时暂停播放广播
+registerReceiver(mNoisyReceiver, mFilter);
+//注销监听来电/耳机拔出时暂停播放广播
+unregisterReceiver(mNoisyReceiver);
+```
+
+- **1.3.3 看广播接收者中的代码逻辑**
+
+```
+/**
+ * 来电/耳机拔出时暂停播放
+ * 其实这个跟通知处理逻辑一样
+ */
+public class AudioEarPhoneReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if(action!=null && action.length()>0){
+            switch (action){
+                //来电/耳机拔出时暂停播放
+                case AudioManager.ACTION_AUDIO_BECOMING_NOISY:
+                    PlayService.startCommand(context, MusicPlayAction.TYPE_START_PAUSE);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
+```
+
+
+### 2 耳机线控,耳机控制声音
+#### 2.1 耳机按键也可以控制音量调节
+- 不得不说Android手机需要考虑不同情况,就拿耳机来说,有的有音控,有的没有音控,有的手机支持,有的手机不支持,虽然说不太重要,但还是可以思考一下。目前参考大量的案例,只能解决大部分的正常控制声音功能。
+- 刚开始看到这个真是蒙圈了,不过有Google,不担心。对于程序员来说没有实现不了的功能,只有自己的技术过不过关,哈哈,同事说的……找到了答案
+
+#### 2.2 在5.0之前和5.0之后比较
+- 对于5.0以上系统的手机,激活了MediaSession,就可以不用关心耳机声控了,会自己实现。
+- 对于5.0以前系统的手机,还是需要自己手动设置相关属性的,需要自己手动实现
+
+#### 2.5.3 实现的逻辑分析
+- 这里暂且只是讨论5.0以前的处理方法,需要自己监听耳机按键
+- 通过代码KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);获取到KeyEvent的对象,然后获取对应点击事件的code,然后再做播放暂停,上一首,下一首处理。具体代码可以参考EarphoneControlReceiver类
+
+
+### 3.蓝牙耳机特殊处理
+#### 3.1 如何监听蓝牙耳机打开关闭
+#### 3.2 如何监听蓝牙耳机按键调节声控功能
+- 探索中,欢迎有好的想法分享出来
+
+
+
+### 4.外放,耳机,听筒之间的切换
+#### 4.1 相关概念讲解
+- 在Android系统中是用AudioManager来管理播放模式的,通过AudioManager.setMode()方法来实现.
+- 在setMode()方法中有以下几种对应不同的播放模式:
+
+```
+MODE_NORMAL: 普通模式,既不是铃声模式也不是通话模式
+MODE_RINGTONE:铃声模式
+MODE_IN_CALL:通话模式
+MODE_IN_COMMUNICATION:通信模式,包括音/视频,VoIP通话.(3.0加入的,与通话模式类似)
+其中:
+播放音乐的对应的就是MODE_NORMAL, 如果使用外放播则调用audioManager.setSpeakerphoneOn(true)即可.
+```
+
+#### 4.2 代码展示案例
+- 若使用耳机和听筒,则需要先设置模式为MODE_IN_CALL(3.0以前)或MODE_IN_COMMUNICATION(3.0以后).
+
+```
+public class AudioSoundManager  {
+    private AudioManager mAudioManager;
+    /**
+     * 初始化操作
+     * @param content           playService对象
+     */
+    public AudioSoundManager(@NonNull PlayService content) {
+        mAudioManager = (AudioManager) content.getSystemService(AUDIO_SERVICE);
+    }
+
+    /**
+     * 切换到外放
+     */
+    public void changeToSpeaker(){
+        mAudioManager.setMode(AudioManager.MODE_NORMAL);
+        mAudioManager.setSpeakerphoneOn(true);
+    }
+
+    /**
+     * 切换到耳机模式
+     */
+    public void changeToHeadset(){
+        mAudioManager.setSpeakerphoneOn(false);
+    }
+
+    /**
+     * 切换到听筒
+     */
+    @SuppressLint("ObsoleteSdkInt")
+    public void changeToReceiver(){
+        mAudioManager.setSpeakerphoneOn(false);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
+            mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+        } else {
+            mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+        }
+    }
+}
+```
+
+
+### 5.其他问题说明
+#### 5.1 版本更新情况
+- v1.0.0 2017年12月8日
+- v1.0.1 2018年2月2日
+
+#### 5.2 参考链接
+- Android 耳机事件传递流程:http://blog.csdn.net/frakie_kwok/article/details/73729804
+- 耳机拔出事件:https://www.jianshu.com/p/3aa3197d7ec1
+- Android音乐播放模式切换-外放、听筒、耳机:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1007/3548.html
+
+
+
+
+
+
+
+
+
+