1/* 2 * Copyright (C) 2015 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.documentsui.dirlist; 18 19import android.annotation.ColorInt; 20import android.content.Context; 21import android.database.Cursor; 22import android.graphics.Rect; 23import android.support.annotation.Nullable; 24import android.support.v7.widget.RecyclerView; 25import android.view.KeyEvent; 26import android.view.LayoutInflater; 27import android.view.MotionEvent; 28import android.view.View; 29import android.view.ViewGroup; 30 31import com.android.documentsui.Events; 32import com.android.documentsui.R; 33import com.android.documentsui.State; 34 35public abstract class DocumentHolder 36 extends RecyclerView.ViewHolder 37 implements View.OnKeyListener { 38 39 static final float DISABLED_ALPHA = 0.3f; 40 41 public @Nullable String modelId; 42 43 final Context mContext; 44 final @ColorInt int mDefaultBgColor; 45 final @ColorInt int mSelectedBgColor; 46 47 DocumentHolder.EventListener mEventListener; 48 private View.OnKeyListener mKeyListener; 49 private View mSelectionHotspot; 50 51 52 public DocumentHolder(Context context, ViewGroup parent, int layout) { 53 this(context, inflateLayout(context, parent, layout)); 54 } 55 56 public DocumentHolder(Context context, View item) { 57 super(item); 58 59 itemView.setOnKeyListener(this); 60 61 mContext = context; 62 63 mDefaultBgColor = context.getColor(R.color.item_doc_background); 64 mSelectedBgColor = context.getColor(R.color.item_doc_background_selected); 65 66 mSelectionHotspot = itemView.findViewById(R.id.icon_check); 67 } 68 69 /** 70 * Binds the view to the given item data. 71 * @param cursor 72 * @param modelId 73 * @param state 74 */ 75 public abstract void bind(Cursor cursor, String modelId, State state); 76 77 /** 78 * Makes the associated item view appear selected. Note that this merely affects the appearance 79 * of the view, it doesn't actually select the item. 80 * TODO: Use the DirectoryItemAnimator instead of manually controlling animation using a boolean 81 * flag. 82 * 83 * @param selected 84 * @param animate Whether or not to animate the change. Only selection changes initiated by the 85 * selection manager should be animated. See 86 * {@link ModelBackedDocumentsAdapter#onBindViewHolder(DocumentHolder, int, java.util.List)} 87 */ 88 public void setSelected(boolean selected, boolean animate) { 89 // Note: the animate param doesn't apply for this base implementation, because the 90 // DirectoryItemAnimator takes care of it. It's required by subclasses, which perform their 91 // own animation. 92 itemView.setActivated(selected); 93 itemView.setBackgroundColor(selected ? mSelectedBgColor : mDefaultBgColor); 94 } 95 96 /** 97 * Highlights the associated item view. 98 * @param highlighted 99 */ 100 public void setHighlighted(boolean highlighted) { 101 itemView.setBackgroundColor(highlighted ? mSelectedBgColor : mDefaultBgColor); 102 } 103 104 public void setEnabled(boolean enabled) { 105 setEnabledRecursive(itemView, enabled); 106 } 107 108 @Override 109 public boolean onKey(View v, int keyCode, KeyEvent event) { 110 // Event listener should always be set. 111 assert(mEventListener != null); 112 113 return mEventListener.onKey(this, keyCode, event); 114 } 115 116 public void addEventListener(DocumentHolder.EventListener listener) { 117 // Just handle one for now; switch to a list if necessary. 118 assert(mEventListener == null); 119 mEventListener = listener; 120 } 121 122 public void addOnKeyListener(View.OnKeyListener listener) { 123 // Just handle one for now; switch to a list if necessary. 124 assert(mKeyListener == null); 125 mKeyListener = listener; 126 } 127 128 public boolean onSingleTapUp(MotionEvent event) { 129 if (Events.isMouseEvent(event)) { 130 // Mouse clicks select. 131 // TODO: && input.isPrimaryButtonPressed(), but it is returning false. 132 if (mEventListener != null) { 133 return mEventListener.onSelect(this); 134 } 135 } else if (Events.isTouchEvent(event)) { 136 // Touch events select if they occur in the selection hotspot, otherwise they activate. 137 if (mEventListener == null) { 138 return false; 139 } 140 141 // Do everything in global coordinates - it makes things simpler. 142 int[] coords = new int[2]; 143 mSelectionHotspot.getLocationOnScreen(coords); 144 Rect rect = new Rect(coords[0], coords[1], coords[0] + mSelectionHotspot.getWidth(), 145 coords[1] + mSelectionHotspot.getHeight()); 146 147 // If the tap occurred within the icon rect, consider it a selection. 148 if (rect.contains((int) event.getRawX(), (int) event.getRawY())) { 149 return mEventListener.onSelect(this); 150 } else { 151 return mEventListener.onActivate(this); 152 } 153 } 154 return false; 155 } 156 157 static void setEnabledRecursive(View itemView, boolean enabled) { 158 if (itemView == null) return; 159 if (itemView.isEnabled() == enabled) return; 160 itemView.setEnabled(enabled); 161 162 if (itemView instanceof ViewGroup) { 163 final ViewGroup vg = (ViewGroup) itemView; 164 for (int i = vg.getChildCount() - 1; i >= 0; i--) { 165 setEnabledRecursive(vg.getChildAt(i), enabled); 166 } 167 } 168 } 169 170 private static View inflateLayout(Context context, ViewGroup parent, int layout) { 171 final LayoutInflater inflater = LayoutInflater.from(context); 172 return inflater.inflate(layout, parent, false); 173 } 174 175 /** 176 * Implement this in order to be able to respond to events coming from DocumentHolders. 177 */ 178 interface EventListener { 179 /** 180 * Handles activation events on the document holder. 181 * 182 * @param doc The target DocumentHolder 183 * @return Whether the event was handled. 184 */ 185 public boolean onActivate(DocumentHolder doc); 186 187 /** 188 * Handles selection events on the document holder. 189 * 190 * @param doc The target DocumentHolder 191 * @return Whether the event was handled. 192 */ 193 public boolean onSelect(DocumentHolder doc); 194 195 /** 196 * Handles key events on the document holder. 197 * 198 * @param doc The target DocumentHolder. 199 * @param keyCode Key code for the event. 200 * @param event KeyEvent for the event. 201 * @return Whether the event was handled. 202 */ 203 public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event); 204 } 205} 206