/* * This is the source code of Telegram for Android v. 3.x.x. * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * * Copyright Nikolai Kudashov, 2013-2016. */ package org.telegram.ui.ActionBar; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.os.Build; import android.view.Gravity; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ListView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; import org.telegram.messenger.AnimationCompat.AnimatorListenerAdapterProxy; import org.telegram.messenger.AnimationCompat.AnimatorSetProxy; import org.telegram.messenger.AnimationCompat.ObjectAnimatorProxy; public class DrawerLayoutContainer extends FrameLayout { private static final int MIN_DRAWER_MARGIN = 64; private ViewGroup drawerLayout; private ActionBarLayout parentActionBarLayout; private boolean maybeStartTracking = false; private boolean startedTracking = false; private int startedTrackingX; private int startedTrackingY; private int startedTrackingPointerId; private VelocityTracker velocityTracker = null; private boolean beginTrackingSent; private AnimatorSetProxy currentAnimation = null; private Paint scrimPaint = new Paint(); private Object lastInsets; private boolean inLayout; private int minDrawerMargin; private float scrimOpacity; private Drawable shadowLeft; private boolean allowOpenDrawer; private float drawerPosition = 0; private boolean drawerOpened = false; private boolean allowDrawContent = true; public DrawerLayoutContainer(Context context) { super(context); minDrawerMargin = (int) (MIN_DRAWER_MARGIN * AndroidUtilities.density + 0.5f); setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); setFocusableInTouchMode(true); if (Build.VERSION.SDK_INT >= 21) { setFitsSystemWindows(true); setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener() { @SuppressLint("NewApi") @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { final DrawerLayoutContainer drawerLayout = (DrawerLayoutContainer) v; lastInsets = insets; drawerLayout.setWillNotDraw(insets.getSystemWindowInsetTop() <= 0 && getBackground() == null); drawerLayout.requestLayout(); return insets.consumeSystemWindowInsets(); } }); setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } shadowLeft = getResources().getDrawable(R.drawable.menu_shadow); } @SuppressLint("NewApi") private void dispatchChildInsets(View child, Object insets, int drawerGravity) { WindowInsets wi = (WindowInsets) insets; if (drawerGravity == Gravity.LEFT) { wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); } else if (drawerGravity == Gravity.RIGHT) { wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); } child.dispatchApplyWindowInsets(wi); } @SuppressLint("NewApi") private void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity, boolean topOnly) { WindowInsets wi = (WindowInsets) insets; if (drawerGravity == Gravity.LEFT) { wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); } else if (drawerGravity == Gravity.RIGHT) { wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); } lp.leftMargin = wi.getSystemWindowInsetLeft(); lp.topMargin = topOnly ? 0 : wi.getSystemWindowInsetTop(); lp.rightMargin = wi.getSystemWindowInsetRight(); lp.bottomMargin = wi.getSystemWindowInsetBottom(); } private int getTopInset(Object insets) { if (Build.VERSION.SDK_INT >= 21) { return insets != null ? ((WindowInsets) insets).getSystemWindowInsetTop() : 0; } return 0; } public void setDrawerLayout(ViewGroup layout) { drawerLayout = layout; addView(drawerLayout); if (Build.VERSION.SDK_INT >= 21) { drawerLayout.setFitsSystemWindows(true); } } public void moveDrawerByX(float dx) { setDrawerPosition(drawerPosition + dx); } public void setDrawerPosition(float value) { drawerPosition = value; if (drawerPosition > drawerLayout.getMeasuredWidth()) { drawerPosition = drawerLayout.getMeasuredWidth(); } else if (drawerPosition < 0) { drawerPosition = 0; } requestLayout(); final int newVisibility = drawerPosition > 0 ? VISIBLE : GONE; if (drawerLayout.getVisibility() != newVisibility) { drawerLayout.setVisibility(newVisibility); } setScrimOpacity(drawerPosition / (float) drawerLayout.getMeasuredWidth()); } public float getDrawerPosition() { return drawerPosition; } public void cancelCurrentAnimation() { if (currentAnimation != null) { currentAnimation.cancel(); currentAnimation = null; } } public void openDrawer(boolean fast) { if (!allowOpenDrawer) { return; } if (AndroidUtilities.isTablet() && parentActionBarLayout != null && parentActionBarLayout.parentActivity != null) { AndroidUtilities.hideKeyboard(parentActionBarLayout.parentActivity.getCurrentFocus()); } cancelCurrentAnimation(); AnimatorSetProxy animatorSet = new AnimatorSetProxy(); animatorSet.playTogether( ObjectAnimatorProxy.ofFloat(this, "drawerPosition", drawerLayout.getMeasuredWidth()) ); animatorSet.setInterpolator(new DecelerateInterpolator()); if (fast) { animatorSet.setDuration(Math.max((int) (200.0f / drawerLayout.getMeasuredWidth() * (drawerLayout.getMeasuredWidth() - drawerPosition)), 50)); } else { animatorSet.setDuration(300); } animatorSet.addListener(new AnimatorListenerAdapterProxy() { @Override public void onAnimationEnd(Object animator) { onDrawerAnimationEnd(true); } }); animatorSet.start(); currentAnimation = animatorSet; } public void closeDrawer(boolean fast) { cancelCurrentAnimation(); AnimatorSetProxy animatorSet = new AnimatorSetProxy(); animatorSet.playTogether( ObjectAnimatorProxy.ofFloat(this, "drawerPosition", 0) ); animatorSet.setInterpolator(new DecelerateInterpolator()); if (fast) { animatorSet.setDuration(Math.max((int) (200.0f / drawerLayout.getMeasuredWidth() * drawerPosition), 50)); } else { animatorSet.setDuration(300); } animatorSet.addListener(new AnimatorListenerAdapterProxy() { @Override public void onAnimationEnd(Object animator) { onDrawerAnimationEnd(false); } }); animatorSet.start(); } private void onDrawerAnimationEnd(boolean opened) { startedTracking = false; currentAnimation = null; drawerOpened = opened; if (!opened) { if (drawerLayout instanceof ListView) { ((ListView)drawerLayout).setSelectionFromTop(0, 0); } } } private void setScrimOpacity(float value) { scrimOpacity = value; invalidate(); } private float getScrimOpacity() { return scrimOpacity; } public View getDrawerLayout() { return drawerLayout; } public void setParentActionBarLayout(ActionBarLayout layout) { parentActionBarLayout = layout; } public void setAllowOpenDrawer(boolean value, boolean animated) { allowOpenDrawer = value; if (!allowOpenDrawer && drawerPosition != 0) { if (!animated) { setDrawerPosition(0); onDrawerAnimationEnd(false); } else { closeDrawer(true); } } } private void prepareForDrawerOpen(MotionEvent ev) { maybeStartTracking = false; startedTracking = true; if (ev != null) { startedTrackingX = (int) ev.getX(); } beginTrackingSent = false; } public boolean isDrawerOpened() { return drawerOpened; } public void setAllowDrawContent(boolean value) { if (allowDrawContent != value) { allowDrawContent = value; invalidate(); } } public boolean onTouchEvent(MotionEvent ev) { if (!parentActionBarLayout.checkTransitionAnimation()) { if (drawerOpened && ev != null && ev.getX() > drawerPosition && !startedTracking) { if (ev.getAction() == MotionEvent.ACTION_UP) { closeDrawer(false); } return true; } if (allowOpenDrawer && parentActionBarLayout.fragmentsStack.size() == 1) { if (ev != null && (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_MOVE) && !startedTracking && !maybeStartTracking) { startedTrackingPointerId = ev.getPointerId(0); maybeStartTracking = true; startedTrackingX = (int) ev.getX(); startedTrackingY = (int) ev.getY(); cancelCurrentAnimation(); if (velocityTracker != null) { velocityTracker.clear(); } } else if (ev != null && ev.getAction() == MotionEvent.ACTION_MOVE && ev.getPointerId(0) == startedTrackingPointerId) { if (velocityTracker == null) { velocityTracker = VelocityTracker.obtain(); } float dx = (int) (ev.getX() - startedTrackingX); float dy = Math.abs((int) ev.getY() - startedTrackingY); velocityTracker.addMovement(ev); if (maybeStartTracking && !startedTracking && (dx > 0 && dx / 3.0f > Math.abs(dy) && Math.abs(dx) >= AndroidUtilities.getPixelsInCM(0.2f, true) || dx < 0 && Math.abs(dx) >= Math.abs(dy) && Math.abs(dx) >= AndroidUtilities.getPixelsInCM(0.4f, true))) { prepareForDrawerOpen(ev); startedTrackingX = (int) ev.getX(); requestDisallowInterceptTouchEvent(true); } else if (startedTracking) { if (!beginTrackingSent) { if (((Activity)getContext()).getCurrentFocus() != null) { AndroidUtilities.hideKeyboard(((Activity)getContext()).getCurrentFocus()); } beginTrackingSent = true; } moveDrawerByX(dx); startedTrackingX = (int) ev.getX(); } } else if (ev == null || ev != null && ev.getPointerId(0) == startedTrackingPointerId && (ev.getAction() == MotionEvent.ACTION_CANCEL || ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_POINTER_UP)) { if (velocityTracker == null) { velocityTracker = VelocityTracker.obtain(); } velocityTracker.computeCurrentVelocity(1000); /*if (!startedTracking) { float velX = velocityTracker.getXVelocity(); float velY = velocityTracker.getYVelocity(); if (Math.abs(velX) >= 3500 && Math.abs(velX) > Math.abs(velY)) { prepareForDrawerOpen(ev); if (!beginTrackingSent) { if (((Activity)getContext()).getCurrentFocus() != null) { AndroidUtilities.hideKeyboard(((Activity)getContext()).getCurrentFocus()); } beginTrackingSent = true; } } }*/ if (startedTracking || drawerPosition != 0 && drawerPosition != drawerLayout.getMeasuredWidth()) { float velX = velocityTracker.getXVelocity(); float velY = velocityTracker.getYVelocity(); boolean backAnimation = drawerPosition < drawerLayout.getMeasuredWidth() / 2.0f && (velX < 3500 || Math.abs(velX) < Math.abs(velY)) || velX < 0 && Math.abs(velX) >= 3500; if (!backAnimation) { openDrawer(!drawerOpened && Math.abs(velX) >= 3500); } else { closeDrawer(drawerOpened && Math.abs(velX) >= 3500); } startedTracking = false; } else { maybeStartTracking = false; startedTracking = false; } if (velocityTracker != null) { velocityTracker.recycle(); velocityTracker = null; } } } return startedTracking; } return false; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return parentActionBarLayout.checkTransitionAnimation() || onTouchEvent(ev); } @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (maybeStartTracking && !startedTracking) { onTouchEvent(null); } super.requestDisallowInterceptTouchEvent(disallowIntercept); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { inLayout = true; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getVisibility() == GONE) { continue; } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); try { if (drawerLayout != child) { child.layout(lp.leftMargin, lp.topMargin, lp.leftMargin + child.getMeasuredWidth(), lp.topMargin + child.getMeasuredHeight()); } else { child.layout(-child.getMeasuredWidth() + (int)drawerPosition, lp.topMargin, (int)drawerPosition, lp.topMargin + child.getMeasuredHeight()); } } catch (Exception e) { FileLog.e("tmessages", e); } } inLayout = false; } @Override public void requestLayout() { if (!inLayout) { /*StackTraceElement[] elements = Thread.currentThread().getStackTrace(); for (int a = 0; a < elements.length; a++) { FileLog.d("tmessages", "on " + elements[a]); }*/ super.requestLayout(); } } @SuppressLint("NewApi") @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(widthSize, heightSize); final boolean applyInsets = lastInsets != null && Build.VERSION.SDK_INT >= 21; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getVisibility() == GONE) { continue; } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (applyInsets) { if (child.getFitsSystemWindows()) { dispatchChildInsets(child, lastInsets, lp.gravity); } else if (child.getTag() == null) { applyMarginInsets(lp, lastInsets, lp.gravity, Build.VERSION.SDK_INT >= 21); } } if (drawerLayout != child) { final int contentWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); final int contentHeightSpec = MeasureSpec.makeMeasureSpec(heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); child.measure(contentWidthSpec, contentHeightSpec); } else { child.setPadding(0, 0, 0, 0); final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, minDrawerMargin + lp.leftMargin + lp.rightMargin, lp.width); final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, lp.topMargin + lp.bottomMargin, lp.height); child.measure(drawerWidthSpec, drawerHeightSpec); } } } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { if (!allowDrawContent) { return false; } final int height = getHeight(); final boolean drawingContent = child != drawerLayout; int lastVisibleChild = 0; int clipLeft = 0, clipRight = getWidth(); final int restoreCount = canvas.save(); if (drawingContent) { final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View v = getChildAt(i); if (v.getVisibility() == VISIBLE && v != drawerLayout) { lastVisibleChild = i; } if (v == child || v.getVisibility() != VISIBLE || v != drawerLayout || v.getHeight() < height) { continue; } final int vright = v.getRight(); if (vright > clipLeft) { clipLeft = vright; } } if (clipLeft != 0) { canvas.clipRect(clipLeft, 0, clipRight, getHeight()); } } final boolean result = super.drawChild(canvas, child, drawingTime); canvas.restoreToCount(restoreCount); if (scrimOpacity > 0 && drawingContent) { if (indexOfChild(child) == lastVisibleChild) { scrimPaint.setColor((int) (((0x99000000 & 0xff000000) >>> 24) * scrimOpacity) << 24); canvas.drawRect(clipLeft, 0, clipRight, getHeight(), scrimPaint); } } else if (shadowLeft != null) { final float alpha = Math.max(0, Math.min(drawerPosition / AndroidUtilities.dp(20), 1.0f)); if (alpha != 0) { shadowLeft.setBounds((int)drawerPosition, child.getTop(), (int)drawerPosition + shadowLeft.getIntrinsicWidth(), child.getBottom()); shadowLeft.setAlpha((int) (0xff * alpha)); shadowLeft.draw(canvas); } } return result; } @Override public boolean hasOverlappingRendering() { return false; } }