1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.views; 17 18 import android.content.Context; 19 import android.graphics.Canvas; 20 import android.util.AttributeSet; 21 import android.widget.EdgeEffect; 22 import android.widget.RelativeLayout; 23 24 import androidx.annotation.NonNull; 25 import androidx.recyclerview.widget.RecyclerView; 26 import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory; 27 28 import com.android.launcher3.Utilities; 29 30 /** 31 * View group to allow rendering overscroll effect in a child at the parent level 32 */ 33 public class SpringRelativeLayout extends RelativeLayout { 34 35 // fixed edge at the time force is applied 36 private final EdgeEffect mEdgeGlowTop; 37 private final EdgeEffect mEdgeGlowBottom; 38 SpringRelativeLayout(Context context)39 public SpringRelativeLayout(Context context) { 40 this(context, null); 41 } 42 SpringRelativeLayout(Context context, AttributeSet attrs)43 public SpringRelativeLayout(Context context, AttributeSet attrs) { 44 this(context, attrs, 0); 45 } 46 SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr)47 public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { 48 super(context, attrs, defStyleAttr); 49 mEdgeGlowTop = Utilities.ATLEAST_S 50 ? new EdgeEffect(context, attrs) : new EdgeEffect(context); 51 mEdgeGlowBottom = Utilities.ATLEAST_S 52 ? new EdgeEffect(context, attrs) : new EdgeEffect(context); 53 setWillNotDraw(false); 54 } 55 56 @Override draw(Canvas canvas)57 public void draw(Canvas canvas) { 58 super.draw(canvas); 59 if (!mEdgeGlowTop.isFinished()) { 60 final int restoreCount = canvas.save(); 61 canvas.translate(0, 0); 62 mEdgeGlowTop.setSize(getWidth(), getHeight()); 63 if (mEdgeGlowTop.draw(canvas)) { 64 postInvalidateOnAnimation(); 65 } 66 canvas.restoreToCount(restoreCount); 67 } 68 if (!mEdgeGlowBottom.isFinished()) { 69 final int restoreCount = canvas.save(); 70 final int width = getWidth(); 71 final int height = getHeight(); 72 canvas.translate(-width, height); 73 canvas.rotate(180, width, 0); 74 mEdgeGlowBottom.setSize(width, height); 75 if (mEdgeGlowBottom.draw(canvas)) { 76 postInvalidateOnAnimation(); 77 } 78 canvas.restoreToCount(restoreCount); 79 } 80 } 81 82 83 /** 84 * Absorbs the velocity as a result for swipe-up fling 85 */ absorbSwipeUpVelocity(int velocity)86 protected void absorbSwipeUpVelocity(int velocity) { 87 mEdgeGlowBottom.onAbsorb(velocity); 88 invalidate(); 89 } 90 absorbPullDeltaDistance(float deltaDistance, float displacement)91 protected void absorbPullDeltaDistance(float deltaDistance, float displacement) { 92 mEdgeGlowBottom.onPull(deltaDistance, displacement); 93 invalidate(); 94 } 95 onRelease()96 public void onRelease() { 97 mEdgeGlowBottom.onRelease(); 98 } 99 createEdgeEffectFactory()100 public EdgeEffectFactory createEdgeEffectFactory() { 101 return new ProxyEdgeEffectFactory(); 102 } 103 104 private class ProxyEdgeEffectFactory extends EdgeEffectFactory { 105 106 @NonNull @Override createEdgeEffect(RecyclerView view, int direction)107 protected EdgeEffect createEdgeEffect(RecyclerView view, int direction) { 108 if (direction == DIRECTION_TOP) { 109 return new EdgeEffectProxy(getContext(), mEdgeGlowTop); 110 } 111 return super.createEdgeEffect(view, direction); 112 } 113 } 114 115 private class EdgeEffectProxy extends EdgeEffect { 116 117 private final EdgeEffect mParent; 118 EdgeEffectProxy(Context context, EdgeEffect parent)119 EdgeEffectProxy(Context context, EdgeEffect parent) { 120 super(context); 121 mParent = parent; 122 } 123 124 @Override draw(Canvas canvas)125 public boolean draw(Canvas canvas) { 126 return false; 127 } 128 invalidateParentScrollEffect()129 private void invalidateParentScrollEffect() { 130 if (!mParent.isFinished()) { 131 invalidate(); 132 } 133 } 134 135 @Override onAbsorb(int velocity)136 public void onAbsorb(int velocity) { 137 mParent.onAbsorb(velocity); 138 invalidateParentScrollEffect(); 139 } 140 141 @Override onPull(float deltaDistance)142 public void onPull(float deltaDistance) { 143 mParent.onPull(deltaDistance); 144 invalidateParentScrollEffect(); 145 } 146 147 @Override onPull(float deltaDistance, float displacement)148 public void onPull(float deltaDistance, float displacement) { 149 mParent.onPull(deltaDistance, displacement); 150 invalidateParentScrollEffect(); 151 } 152 153 @Override onRelease()154 public void onRelease() { 155 mParent.onRelease(); 156 invalidateParentScrollEffect(); 157 } 158 159 @Override finish()160 public void finish() { 161 mParent.finish(); 162 } 163 164 @Override isFinished()165 public boolean isFinished() { 166 return mParent.isFinished(); 167 } 168 } 169 }