Java tutorial
/* * Copyright (C) 2016 hejunlin <hejunlin2013@gmail.com> * * Github:https://github.com/hejunlin2013/DragVideo * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hejunlin.dragvideo; import android.content.Context; import android.graphics.Color; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import java.lang.ref.WeakReference; public class DragVideoView extends ViewGroup { /** * ?? */ public static final int NONE = 1 << 0; public static final int HORIZONTAL = 1 << 1; public static final int VERTICAL = 1 << 2; /** * ?? */ public static final int SLIDE_RESTORE_ORIGINAL = 1 << 0; public static final int SLIDE_TO_LEFT = 1 << 1; public static final int SLIDE_TO_RIGHT = 1 << 2; /** * ? */ private static final float MIN_ALPHA = 0.2f; /** * ? */ private static final float PLAYER_RATIO = 0.5f; /** * */ private static final float VIDEO_RATIO = 16f / 9f; /** * ??????? */ private static final float ORIGINAL_MIN_OFFSET = 1f / (1f + PLAYER_RATIO); private static final float LEFT_DRAG_DISAPPEAR_OFFSET = (4f - PLAYER_RATIO) / (4f + 4f * PLAYER_RATIO); private static final float RIGHT_DRAG_DISAPPEAR_OFFSET = (4f + PLAYER_RATIO) / (4f + 4f * PLAYER_RATIO); private static final float MAX_OFFSET_RATIO = (1f - PLAYER_RATIO) / (1f + PLAYER_RATIO); private static final String TAG = DragVideoView.class.getSimpleName(); /** * ViewDragHelper */ private CustomViewDragHelper mDragHelper; /** * ViewGroup??2? */ private View mPlayer; private View mDesc; /** * onMeasure */ private boolean mIsFinishInit = false; /** * ?? */ private boolean mIsMinimum = true; /** * ? */ private int mVerticalRange; /** * ? */ private int mHorizontalRange; /** * ?this.getPaddingTop() */ private int mMinTop; /** * top */ private int mTop; /** * left */ private int mLeft; /** * ???? */ private int mPlayerMaxWidth; /** * ????? */ private int mPlayerMinWidth; /** * ?? */ private int mDragDirect = NONE; /** * ??? * (mTop - mMinTop) / mVerticalRange */ private float mVerticalOffset = 1f; /** * ??? * (mLeft + mPlayerMinWidth) / mHorizontalRange) */ private float mHorizontalOffset = ORIGINAL_MIN_OFFSET; /** * */ private WeakReference<Callback> mCallback; /** * ?ACTION_DOWN?? */ private int mDownX; private int mDownY; /** * ? */ private int mDisappearDirect = SLIDE_RESTORE_ORIGINAL; public DragVideoView(Context context) { this(context, null); } public DragVideoView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragVideoView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { mDragHelper = CustomViewDragHelper.create(this, 1f, new MyHelperCallback()); setBackgroundColor(Color.TRANSPARENT); } public void restorePosition() {//??? mPlayer.setAlpha(1f); this.setAlpha(0f);//?DragVideoView??? mLeft = mHorizontalRange - mPlayerMinWidth; mTop = mVerticalRange; mIsMinimum = true; mVerticalOffset = 1f; } public void show() { this.setAlpha(1f);//?DragVideoView?? mDragDirect = VERTICAL; maximize(); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { return mDragHelper.shouldInterceptTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { boolean isHit = mDragHelper.isViewUnder(mPlayer, (int) event.getX(), (int) event.getY()); if (isHit) { switch (MotionEventCompat.getActionMasked(event)) { case MotionEvent.ACTION_DOWN: { mDownX = (int) event.getX(); mDownY = (int) event.getY(); } break; case MotionEvent.ACTION_MOVE: if (mDragDirect == NONE) { int dx = Math.abs(mDownX - (int) event.getX());//getX()MOVEgetX() int dy = Math.abs(mDownY - (int) event.getY());//getY()MOVEgetY() int slop = mDragHelper.getTouchSlop();//?? if (Math.sqrt(dx * dx + dy * dy) >= slop) {//?? if (dy >= dx) mDragDirect = VERTICAL; else mDragDirect = HORIZONTAL; } } break; case MotionEvent.ACTION_UP: { if (mDragDirect == NONE) { int dx = Math.abs(mDownX - (int) event.getX()); int dy = Math.abs(mDownY - (int) event.getY()); int slop = mDragHelper.getTouchSlop(); if (Math.sqrt(dx * dx + dy * dy) < slop) { mDragDirect = VERTICAL; if (mIsMinimum) maximize(); else minimize(); } } } break; default: break; } } mDragHelper.processTouchEvent(event); return isHit; } private void maximize() { mIsMinimum = false; slideVerticalTo(0f); } private void minimize() { mIsMinimum = true; slideVerticalTo(1f); } private boolean slideVerticalTo(float slideOffset) {//??? int topBound = mMinTop; int y = (int) (topBound + slideOffset * mVerticalRange); if (mDragHelper.smoothSlideViewTo(mPlayer, mIsMinimum ? (int) (mPlayerMaxWidth * (1 - PLAYER_RATIO)) : getPaddingLeft(), y)) { ViewCompat.postInvalidateOnAnimation(this); return true; } return false; } private void slideToLeft() {// slideHorizontalTo(0f); mDisappearDirect = SLIDE_TO_LEFT; } private void slideToRight() {//? slideHorizontalTo(1f); mDisappearDirect = SLIDE_TO_RIGHT; } private void slideToOriginalPosition() {// slideHorizontalTo(ORIGINAL_MIN_OFFSET); mDisappearDirect = SLIDE_RESTORE_ORIGINAL; } private boolean slideHorizontalTo(float slideOffset) {//??? int leftBound = -mPlayer.getWidth(); int x = (int) (leftBound + slideOffset * mHorizontalRange); if (mDragHelper.smoothSlideViewTo(mPlayer, x, mTop)) { ViewCompat.postInvalidateOnAnimation(this); return true; } return false; } private class MyHelperCallback extends CustomViewDragHelper.Callback { //CustomViewDragHelperCallback @Override public boolean tryCaptureView(View child, int pointerId) {//?view?? return child == mPlayer; //view } @Override public void onViewDragStateChanged(int state) { //ViewDragHelper???IDLE,DRAGGING,SETTING[] if (state == CustomViewDragHelper.STATE_IDLE) { if (mIsMinimum && mDragDirect == HORIZONTAL && mDisappearDirect != SLIDE_RESTORE_ORIGINAL) { if (mCallback != null && mCallback.get() != null) mCallback.get().onDisappear(mDisappearDirect);//? mDisappearDirect = SLIDE_RESTORE_ORIGINAL; restorePosition(); requestLayoutLightly(); } mDragDirect = NONE; } } @Override public int getViewVerticalDragRange(View child) { //?? int range = 0; if (child == mPlayer && mDragDirect == VERTICAL) { range = mVerticalRange; } Log.d(TAG, ">> getViewVerticalDragRange-range:" + range); return range; } @Override public int getViewHorizontalDragRange(View child) { //?? int range = 0; if (child == mPlayer && mIsMinimum && mDragDirect == HORIZONTAL) { range = mHorizontalRange; } Log.d(TAG, ">> getViewHorizontalDragRange-range:" + range); return range; } @Override public int clampViewPositionVertical(View child, int top, int dy) {//childleft , top ?? int newTop = mTop; Log.d(TAG, ">> clampViewPositionVertical:" + top + "," + dy); if (child == mPlayer && mDragDirect == VERTICAL) { int topBound = mMinTop; int bottomBound = topBound + mVerticalRange; newTop = Math.min(Math.max(top, topBound), bottomBound); } Log.d(TAG, ">> clampViewPositionVertical:newTop-" + newTop); return newTop; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { //???? int newLeft = mLeft; Log.d(TAG, ">> clampViewPositionHorizontal:" + left + "," + dx); if (child == mPlayer && mIsMinimum && mDragDirect == HORIZONTAL) { int leftBound = -mPlayer.getWidth(); int rightBound = leftBound + mHorizontalRange; newLeft = Math.min(Math.max(left, leftBound), rightBound); } Log.d(TAG, ">> clampViewPositionHorizontal:newLeft-" + newLeft + ",mLeft-" + mLeft); return newLeft; } @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { //view???? Log.d(TAG, ">> onViewPositionChanged:" + "mDragDirect-" + mDragDirect + ",left-" + left + ",top-" + top + ",mLeft-" + mLeft); Log.d(TAG, ">> onViewPositionChanged-mPlayer:left-" + mPlayer.getLeft() + ",top-" + mPlayer.getTop()); if (mDragDirect == VERTICAL) { //? mTop = top; mVerticalOffset = (float) (mTop - mMinTop) / mVerticalRange; } else if (mIsMinimum && mDragDirect == HORIZONTAL) { // ? mLeft = left; mHorizontalOffset = Math.abs((float) (mLeft + mPlayerMinWidth) / mHorizontalRange); } requestLayoutLightly(); } @Override public void onViewReleased(View releasedChild, float xvel, float yvel) {// if (mDragDirect == VERTICAL) { //?? if (yvel > 0 || (yvel == 0 && mVerticalOffset >= 0.5f)) minimize(); else if (yvel < 0 || (yvel == 0 && mVerticalOffset < 0.5f)) maximize(); } else if (mIsMinimum && mDragDirect == HORIZONTAL) { //???? if ((mHorizontalOffset < LEFT_DRAG_DISAPPEAR_OFFSET && xvel < 0)) slideToLeft(); //? else if ((mHorizontalOffset > RIGHT_DRAG_DISAPPEAR_OFFSET && xvel > 0)) slideToRight();// ?? else slideToOriginalPosition();//? } } } @Override public void computeScroll() { if (mDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } @Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() != 2) throw new RuntimeException("this ViewGroup must only contains 2 views"); mPlayer = getChildAt(0); mDesc = getChildAt(1); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { customMeasure(widthMeasureSpec, heightMeasureSpec); int maxWidth = MeasureSpec.getSize(widthMeasureSpec); int maxHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0), resolveSizeAndState(maxHeight, heightMeasureSpec, 0)); if (!mIsFinishInit) { mMinTop = getPaddingTop(); mPlayerMinWidth = mPlayer.getMeasuredWidth(); // mPlayerMaxWidth = (int)(mPlayerMinWidth / PLAYER_RATIO); mHorizontalRange = mPlayerMaxWidth + mPlayerMinWidth; mVerticalRange = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - mPlayer.getMeasuredHeight(); restorePosition(); mIsFinishInit = true; } } private void customMeasure(int widthMeasureSpec, int heightMeasureSpec) { measurePlayer(widthMeasureSpec, heightMeasureSpec); measureDesc(widthMeasureSpec, heightMeasureSpec); } private void measurePlayer(int widthMeasureSpec, int heightMeasureSpec) { final LayoutParams lp = mPlayer.getLayoutParams(); if (!mIsFinishInit) { int measureWidth = getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight(), lp.width); mPlayerMaxWidth = MeasureSpec.getSize(measureWidth); } justMeasurePlayer(); } private void measureDesc(int widthMeasureSpec, int heightMeasureSpec) { measureChild(mDesc, widthMeasureSpec, heightMeasureSpec); } private void justMeasurePlayer() { int widthCurSize = (int) (mPlayerMaxWidth * (1f - mVerticalOffset * (1f - PLAYER_RATIO))); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthCurSize, MeasureSpec.EXACTLY); int heightSize = (int) (MeasureSpec.getSize(childWidthMeasureSpec) / VIDEO_RATIO); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY); mPlayer.measure(childWidthMeasureSpec, childHeightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { onLayoutLightly(); } private void onLayoutLightly() { if (mDragDirect != HORIZONTAL) { mLeft = this.getWidth() - this.getPaddingRight() - this.getPaddingLeft() - mPlayer.getMeasuredWidth(); mDesc.layout(mLeft, mTop + mPlayer.getMeasuredHeight(), mLeft + mDesc.getMeasuredWidth(), mTop + mDesc.getMeasuredHeight()); } mPlayer.layout(mLeft, mTop, mLeft + mPlayer.getMeasuredWidth(), mTop + mPlayer.getMeasuredHeight()); } private void requestLayoutLightly() { justMeasurePlayer(); onLayoutLightly(); ViewCompat.postInvalidateOnAnimation(this);//? } public void setCallback(Callback callback) { mCallback = new WeakReference<>(callback); } public interface Callback { void onDisappear(int direct); } }