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 /** 29 * View group to allow rendering overscroll effect in a child at the parent level 30 */ 31 public class SpringRelativeLayout extends RelativeLayout { 32 33 // fixed edge at the time force is applied 34 private final EdgeEffect mEdgeGlowTop; 35 private final EdgeEffect mEdgeGlowBottom; 36 SpringRelativeLayout(Context context)37 public SpringRelativeLayout(Context context) { 38 this(context, null); 39 } 40 SpringRelativeLayout(Context context, AttributeSet attrs)41 public SpringRelativeLayout(Context context, AttributeSet attrs) { 42 this(context, attrs, 0); 43 } 44 SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr)45 public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { 46 super(context, attrs, defStyleAttr); 47 mEdgeGlowTop = new EdgeEffect(context, attrs); 48 mEdgeGlowBottom = new EdgeEffect(context, attrs); 49 setWillNotDraw(false); 50 } 51 52 @Override draw(Canvas canvas)53 public void draw(Canvas canvas) { 54 super.draw(canvas); 55 if (!mEdgeGlowTop.isFinished()) { 56 final int restoreCount = canvas.save(); 57 canvas.translate(0, 0); 58 mEdgeGlowTop.setSize(getWidth(), getHeight()); 59 if (mEdgeGlowTop.draw(canvas)) { 60 postInvalidateOnAnimation(); 61 } 62 canvas.restoreToCount(restoreCount); 63 } 64 if (!mEdgeGlowBottom.isFinished()) { 65 final int restoreCount = canvas.save(); 66 final int width = getWidth(); 67 final int height = getHeight(); 68 canvas.translate(-width, height); 69 canvas.rotate(180, width, 0); 70 mEdgeGlowBottom.setSize(width, height); 71 if (mEdgeGlowBottom.draw(canvas)) { 72 postInvalidateOnAnimation(); 73 } 74 canvas.restoreToCount(restoreCount); 75 } 76 } 77 78 79 /** 80 * Absorbs the velocity as a result for swipe-up fling 81 */ absorbSwipeUpVelocity(int velocity)82 protected void absorbSwipeUpVelocity(int velocity) { 83 mEdgeGlowBottom.onAbsorb(velocity); 84 invalidate(); 85 } 86 absorbPullDeltaDistance(float deltaDistance, float displacement)87 protected void absorbPullDeltaDistance(float deltaDistance, float displacement) { 88 mEdgeGlowBottom.onPull(deltaDistance, displacement); 89 invalidate(); 90 } 91 onRelease()92 public void onRelease() { 93 mEdgeGlowBottom.onRelease(); 94 } 95 createEdgeEffectFactory()96 public EdgeEffectFactory createEdgeEffectFactory() { 97 return new ProxyEdgeEffectFactory(); 98 } 99 100 private class ProxyEdgeEffectFactory extends EdgeEffectFactory { 101 102 @NonNull @Override createEdgeEffect(RecyclerView view, int direction)103 protected EdgeEffect createEdgeEffect(RecyclerView view, int direction) { 104 if (direction == DIRECTION_TOP) { 105 return new EdgeEffectProxy(getContext(), mEdgeGlowTop); 106 } 107 return super.createEdgeEffect(view, direction); 108 } 109 } 110 111 private class EdgeEffectProxy extends EdgeEffect { 112 113 private final EdgeEffect mParent; 114 EdgeEffectProxy(Context context, EdgeEffect parent)115 EdgeEffectProxy(Context context, EdgeEffect parent) { 116 super(context); 117 mParent = parent; 118 } 119 120 @Override draw(Canvas canvas)121 public boolean draw(Canvas canvas) { 122 return false; 123 } 124 invalidateParentScrollEffect()125 private void invalidateParentScrollEffect() { 126 if (!mParent.isFinished()) { 127 invalidate(); 128 } 129 } 130 131 @Override onAbsorb(int velocity)132 public void onAbsorb(int velocity) { 133 mParent.onAbsorb(velocity); 134 invalidateParentScrollEffect(); 135 } 136 137 @Override onPull(float deltaDistance)138 public void onPull(float deltaDistance) { 139 mParent.onPull(deltaDistance); 140 invalidateParentScrollEffect(); 141 } 142 143 @Override onPull(float deltaDistance, float displacement)144 public void onPull(float deltaDistance, float displacement) { 145 mParent.onPull(deltaDistance, displacement); 146 invalidateParentScrollEffect(); 147 } 148 149 @Override onRelease()150 public void onRelease() { 151 mParent.onRelease(); 152 invalidateParentScrollEffect(); 153 } 154 155 @Override finish()156 public void finish() { 157 mParent.finish(); 158 } 159 160 @Override isFinished()161 public boolean isFinished() { 162 return mParent.isFinished(); 163 } 164 } 165 }