RecentsVerticalScrollView.java revision fc8fa638617efb5695a1f89ea75375faebbe2a40
1/* 2 * Copyright (C) 2011 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 17package com.android.systemui.recent; 18 19import android.animation.LayoutTransition; 20import android.content.Context; 21import android.content.res.Configuration; 22import android.database.DataSetObserver; 23import android.util.AttributeSet; 24import android.util.Log; 25import android.view.MotionEvent; 26import android.view.View; 27import android.view.ViewConfiguration; 28import android.widget.LinearLayout; 29import android.widget.ScrollView; 30 31import com.android.systemui.R; 32import com.android.systemui.SwipeHelper; 33import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter; 34 35public class RecentsVerticalScrollView extends ScrollView implements SwipeHelper.Callback { 36 private static final String TAG = RecentsPanelView.TAG; 37 private static final boolean DEBUG = RecentsPanelView.DEBUG; 38 private LinearLayout mLinearLayout; 39 private ActivityDescriptionAdapter mAdapter; 40 private RecentsCallback mCallback; 41 protected int mLastScrollPosition; 42 private SwipeHelper mSwipeHelper; 43 44 private OnLongClickListener mOnLongClick = new OnLongClickListener() { 45 public boolean onLongClick(View v) { 46 final View anchorView = v.findViewById(R.id.app_description); 47 mCallback.handleLongPress(v, anchorView); 48 return true; 49 } 50 }; 51 52 public RecentsVerticalScrollView(Context context) { 53 this(context, null); 54 } 55 56 public RecentsVerticalScrollView(Context context, AttributeSet attrs) { 57 super(context, attrs, 0); 58 float densityScale = getResources().getDisplayMetrics().density; 59 float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); 60 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); 61 } 62 63 private int scrollPositionOfMostRecent() { 64 return mLinearLayout.getHeight() - getHeight(); 65 } 66 67 private void update() { 68 mLinearLayout.removeAllViews(); 69 // Once we can clear the data associated with individual item views, 70 // we can get rid of the removeAllViews() and the code below will 71 // recycle them. 72 for (int i = 0; i < mAdapter.getCount(); i++) { 73 View old = null; 74 if (i < mLinearLayout.getChildCount()) { 75 old = mLinearLayout.getChildAt(i); 76 old.setVisibility(View.VISIBLE); 77 } 78 final View view = mAdapter.getView(i, old, mLinearLayout); 79 80 if (old == null) { 81 view.setClickable(true); 82 view.setOnLongClickListener(mOnLongClick); 83 84 final View thumbnail = getChildContentView(view); 85 // thumbnail is set to clickable in the layout file 86 thumbnail.setOnClickListener(new OnClickListener() { 87 public void onClick(View v) { 88 mCallback.handleOnClick(view); 89 } 90 }); 91 92 mLinearLayout.addView(view); 93 } 94 } 95 for (int i = mAdapter.getCount(); i < mLinearLayout.getChildCount(); i++) { 96 mLinearLayout.getChildAt(i).setVisibility(View.GONE); 97 } 98 // Scroll to end after layout. 99 post(new Runnable() { 100 public void run() { 101 mLastScrollPosition = scrollPositionOfMostRecent(); 102 scrollTo(0, mLastScrollPosition); 103 } 104 }); 105 } 106 107 @Override 108 public void removeViewInLayout(final View view) { 109 dismissChild(view); 110 } 111 112 public boolean onInterceptTouchEvent(MotionEvent ev) { 113 if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); 114 return mSwipeHelper.onInterceptTouchEvent(ev) || 115 super.onInterceptTouchEvent(ev); 116 } 117 118 @Override 119 public boolean onTouchEvent(MotionEvent ev) { 120 return mSwipeHelper.onTouchEvent(ev) || 121 super.onTouchEvent(ev); 122 } 123 124 public boolean canChildBeDismissed(View v) { 125 return true; 126 } 127 128 public void dismissChild(View v) { 129 mSwipeHelper.dismissChild(v, 0); 130 } 131 132 public void onChildDismissed(View v) { 133 mLinearLayout.removeView(v); 134 mCallback.handleSwipe(v); 135 } 136 137 public void onBeginDrag(View v) { 138 } 139 140 public View getChildAtPosition(MotionEvent ev) { 141 final float x = ev.getX() + getScrollX(); 142 final float y = ev.getY() + getScrollY(); 143 for (int i = 0; i < mLinearLayout.getChildCount(); i++) { 144 View item = mLinearLayout.getChildAt(i); 145 if (item.getVisibility() == View.VISIBLE 146 && x >= item.getLeft() && x < item.getRight() 147 && y >= item.getTop() && y < item.getBottom()) { 148 return item; 149 } 150 } 151 return null; 152 } 153 154 public View getChildContentView(View v) { 155 return v.findViewById(R.id.app_thumbnail); 156 } 157 158 @Override 159 protected void onFinishInflate() { 160 super.onFinishInflate(); 161 setScrollbarFadingEnabled(true); 162 mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout); 163 final int leftPadding = mContext.getResources() 164 .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin); 165 setOverScrollEffectPadding(leftPadding, 0); 166 } 167 168 @Override 169 protected void onConfigurationChanged(Configuration newConfig) { 170 super.onConfigurationChanged(newConfig); 171 float densityScale = getResources().getDisplayMetrics().density; 172 mSwipeHelper.setDensityScale(densityScale); 173 float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); 174 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 175 } 176 177 private void setOverScrollEffectPadding(int leftPadding, int i) { 178 // TODO Add to (Vertical)ScrollView 179 } 180 181 @Override 182 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 183 super.onSizeChanged(w, h, oldw, oldh); 184 185 // Skip this work if a transition is running; it sets the scroll values independently 186 // and should not have those animated values clobbered by this logic 187 LayoutTransition transition = mLinearLayout.getLayoutTransition(); 188 if (transition != null && transition.isRunning()) { 189 return; 190 } 191 // Keep track of the last visible item in the list so we can restore it 192 // to the bottom when the orientation changes. 193 mLastScrollPosition = scrollPositionOfMostRecent(); 194 195 // This has to happen post-layout, so run it "in the future" 196 post(new Runnable() { 197 public void run() { 198 // Make sure we're still not clobbering the transition-set values, since this 199 // runnable launches asynchronously 200 LayoutTransition transition = mLinearLayout.getLayoutTransition(); 201 if (transition == null || !transition.isRunning()) { 202 scrollTo(0, mLastScrollPosition); 203 } 204 } 205 }); 206 } 207 208 @Override 209 protected void onVisibilityChanged(View changedView, int visibility) { 210 super.onVisibilityChanged(changedView, visibility); 211 // scroll to bottom after reloading 212 if (visibility == View.VISIBLE && changedView == this) { 213 post(new Runnable() { 214 public void run() { 215 update(); 216 } 217 }); 218 } 219 } 220 221 public void setAdapter(ActivityDescriptionAdapter adapter) { 222 mAdapter = adapter; 223 mAdapter.registerDataSetObserver(new DataSetObserver() { 224 public void onChanged() { 225 update(); 226 } 227 228 public void onInvalidated() { 229 update(); 230 } 231 }); 232 } 233 234 @Override 235 public void setLayoutTransition(LayoutTransition transition) { 236 // The layout transition applies to our embedded LinearLayout 237 mLinearLayout.setLayoutTransition(transition); 238 } 239 240 public void setCallback(RecentsCallback callback) { 241 mCallback = callback; 242 } 243} 244