blob: 6ed1b5c432caf74a7c9dd29373722ea1ca526789 [file] [log] [blame]
Ben Linb8c54e72016-06-10 12:13:27 -07001/*
2 * Copyright (C) 2016 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;
18
19import android.content.Context;
20import android.support.v7.widget.LinearLayoutManager;
21import android.support.v7.widget.RecyclerView;
22import android.util.AttributeSet;
23import android.view.GestureDetector;
24import android.view.LayoutInflater;
25import android.view.MotionEvent;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.ImageView;
Ben Linb8c54e72016-06-10 12:13:27 -070029
30import com.android.documentsui.NavigationViewManager.Breadcrumb;
31import com.android.documentsui.NavigationViewManager.Environment;
Steve McKayd0805062016-09-15 14:30:38 -070032import com.android.documentsui.base.DocumentInfo;
33import com.android.documentsui.base.RootInfo;
Ben Linb8c54e72016-06-10 12:13:27 -070034
35import java.util.function.Consumer;
36
37/**
38 * Horizontal implementation of breadcrumb used for tablet / desktop device layouts
39 */
Ben Linba1cb3d2016-06-20 17:08:22 -070040public final class HorizontalBreadcrumb extends RecyclerView
41 implements Breadcrumb, ItemDragListener.DragHost {
42
43 private static final int USER_NO_SCROLL_OFFSET_THRESHOLD = 5;
Ben Linb8c54e72016-06-10 12:13:27 -070044
45 private LinearLayoutManager mLayoutManager;
46 private BreadcrumbAdapter mAdapter;
47 private Consumer<Integer> mListener;
48
49 public HorizontalBreadcrumb(Context context, AttributeSet attrs, int defStyleAttr) {
50 super(context, attrs, defStyleAttr);
51 }
52
53 public HorizontalBreadcrumb(Context context, AttributeSet attrs) {
54 super(context, attrs);
55 }
56
57 public HorizontalBreadcrumb(Context context) {
58 super(context);
59 }
60
61 @Override
62 public void setup(Environment env,
Steve McKayd9caa6a2016-09-15 16:36:45 -070063 com.android.documentsui.base.State state,
Ben Linb8c54e72016-06-10 12:13:27 -070064 Consumer<Integer> listener) {
65
66 mListener = listener;
67 mLayoutManager = new LinearLayoutManager(
68 getContext(), LinearLayoutManager.HORIZONTAL, false);
Ben Linba1cb3d2016-06-20 17:08:22 -070069 mAdapter = new BreadcrumbAdapter(
70 state, env, new ItemDragListener<>(this));
Ben Linb8c54e72016-06-10 12:13:27 -070071
72 setLayoutManager(mLayoutManager);
73 addOnItemTouchListener(new ClickListener(getContext(), this::onSingleTapUp));
74 }
75
76 @Override
77 public void show(boolean visibility) {
78 if (visibility) {
79 setVisibility(VISIBLE);
Ben Linba1cb3d2016-06-20 17:08:22 -070080 boolean shouldScroll = !hasUserDefineScrollOffset();
81 if (getAdapter() == null) {
82 setAdapter(mAdapter);
83 } else {
84 int currentItemCount = mAdapter.getItemCount();
85 int lastItemCount = mAdapter.getLastItemSize();
86 if (currentItemCount > lastItemCount) {
87 mAdapter.notifyItemRangeInserted(lastItemCount,
88 currentItemCount - lastItemCount);
89 mAdapter.notifyItemChanged(lastItemCount - 1);
90 } else if (currentItemCount < lastItemCount) {
91 mAdapter.notifyItemRangeRemoved(currentItemCount,
92 lastItemCount - currentItemCount);
93 mAdapter.notifyItemChanged(currentItemCount - 1);
94 }
95 }
96 if (shouldScroll) {
97 mLayoutManager.scrollToPosition(mAdapter.getItemCount() - 1);
98 }
Ben Linb8c54e72016-06-10 12:13:27 -070099 } else {
100 setVisibility(GONE);
101 setAdapter(null);
102 }
Ben Linba1cb3d2016-06-20 17:08:22 -0700103 mAdapter.updateLastItemSize();
104 }
105
106 private boolean hasUserDefineScrollOffset() {
107 final int maxOffset = computeHorizontalScrollRange() - computeHorizontalScrollExtent();
108 return (maxOffset - computeHorizontalScrollOffset() > USER_NO_SCROLL_OFFSET_THRESHOLD);
Ben Linb8c54e72016-06-10 12:13:27 -0700109 }
110
111 @Override
112 public void postUpdate() {
113 }
114
Ben Linba1cb3d2016-06-20 17:08:22 -0700115 @Override
116 public void runOnUiThread(Runnable runnable) {
117 post(runnable);
118 }
119
120 @Override
121 public void setDropTargetHighlight(View v, boolean highlight) {
122 RecyclerView.ViewHolder vh = getChildViewHolder(v);
123 if (vh instanceof BreadcrumbHolder) {
124 ((BreadcrumbHolder) vh).setHighlighted(highlight);
125 }
126 }
127
128 @Override
129 public void onViewHovered(View v) {
130 int pos = getChildAdapterPosition(v);
131 if (pos != mAdapter.getItemCount() - 1) {
132 mListener.accept(pos);
133 }
134 }
135
Ben Linb8c54e72016-06-10 12:13:27 -0700136 private void onSingleTapUp(MotionEvent e) {
137 View itemView = findChildViewUnder(e.getX(), e.getY());
138 int pos = getChildAdapterPosition(itemView);
139 if (pos != mAdapter.getItemCount() - 1) {
140 mListener.accept(pos);
141 }
142 }
143
144 private static final class BreadcrumbAdapter
145 extends RecyclerView.Adapter<BreadcrumbHolder> {
146
Ben Linba1cb3d2016-06-20 17:08:22 -0700147 private final Environment mEnv;
Steve McKayd9caa6a2016-09-15 16:36:45 -0700148 private final com.android.documentsui.base.State mState;
Ben Linba1cb3d2016-06-20 17:08:22 -0700149 private final OnDragListener mDragListener;
150 // We keep the old item size so the breadcrumb will only re-render views that are necessary
151 private int mLastItemSize;
Ben Linb8c54e72016-06-10 12:13:27 -0700152
Steve McKayd9caa6a2016-09-15 16:36:45 -0700153 public BreadcrumbAdapter(com.android.documentsui.base.State state,
Ben Linba1cb3d2016-06-20 17:08:22 -0700154 Environment env,
155 OnDragListener dragListener) {
Ben Linb8c54e72016-06-10 12:13:27 -0700156 mState = state;
157 mEnv = env;
Ben Linba1cb3d2016-06-20 17:08:22 -0700158 mDragListener = dragListener;
159 mLastItemSize = mState.stack.size();
Ben Linb8c54e72016-06-10 12:13:27 -0700160 }
161
162 @Override
163 public BreadcrumbHolder onCreateViewHolder(ViewGroup parent, int viewType) {
164 View v = LayoutInflater.from(parent.getContext())
165 .inflate(R.layout.navigation_breadcrumb_item, null);
166 return new BreadcrumbHolder(v);
167 }
168
169 @Override
170 public void onBindViewHolder(BreadcrumbHolder holder, int position) {
171 final DocumentInfo doc = getItem(position);
Ben Lin9cad6352016-07-06 11:58:05 -0700172 final int horizontalPadding = (int) holder.itemView.getResources()
173 .getDimension(R.dimen.breadcrumb_item_padding);
Ben Linb8c54e72016-06-10 12:13:27 -0700174
175 if (position == 0) {
176 final RootInfo root = mEnv.getCurrentRoot();
177 holder.title.setText(root.title);
Ben Lin9cad6352016-07-06 11:58:05 -0700178 holder.title.setPadding(0, 0, horizontalPadding, 0);
Ben Linb8c54e72016-06-10 12:13:27 -0700179 } else {
180 holder.title.setText(doc.displayName);
Ben Lin9cad6352016-07-06 11:58:05 -0700181 holder.title.setPadding(horizontalPadding, 0, horizontalPadding, 0);
Ben Linb8c54e72016-06-10 12:13:27 -0700182 }
183
184 if (position == getItemCount() - 1) {
185 holder.arrow.setVisibility(View.GONE);
Ben Linba1cb3d2016-06-20 17:08:22 -0700186 } else {
187 holder.arrow.setVisibility(View.VISIBLE);
Ben Linb8c54e72016-06-10 12:13:27 -0700188 }
Ben Linba1cb3d2016-06-20 17:08:22 -0700189 holder.itemView.setOnDragListener(mDragListener);
Ben Linb8c54e72016-06-10 12:13:27 -0700190 }
191
192 private DocumentInfo getItem(int position) {
193 return mState.stack.get(mState.stack.size() - position - 1);
194 }
195
196 @Override
197 public int getItemCount() {
198 return mState.stack.size();
199 }
Ben Linba1cb3d2016-06-20 17:08:22 -0700200
201 public int getLastItemSize() {
202 return mLastItemSize;
203 }
204
205 public void updateLastItemSize() {
206 mLastItemSize = mState.stack.size();
207 }
Ben Linb8c54e72016-06-10 12:13:27 -0700208 }
209
210 private static class BreadcrumbHolder extends RecyclerView.ViewHolder {
211
Ben Linba1cb3d2016-06-20 17:08:22 -0700212 protected DragOverTextView title;
Ben Linb8c54e72016-06-10 12:13:27 -0700213 protected ImageView arrow;
214
215 public BreadcrumbHolder(View itemView) {
216 super(itemView);
Ben Linba1cb3d2016-06-20 17:08:22 -0700217 title = (DragOverTextView) itemView.findViewById(R.id.breadcrumb_text);
Ben Linb8c54e72016-06-10 12:13:27 -0700218 arrow = (ImageView) itemView.findViewById(R.id.breadcrumb_arrow);
219 }
Ben Linba1cb3d2016-06-20 17:08:22 -0700220
221 /**
222 * Highlights the associated item view.
223 * @param highlighted
224 */
225 public void setHighlighted(boolean highlighted) {
226 title.setHighlight(highlighted);
227 }
Ben Linb8c54e72016-06-10 12:13:27 -0700228 }
229
230 private static final class ClickListener extends GestureDetector
231 implements OnItemTouchListener {
232
233 public ClickListener(Context context, Consumer<MotionEvent> listener) {
234 super(context, new SimpleOnGestureListener() {
235 @Override
236 public boolean onSingleTapUp(MotionEvent e) {
237 listener.accept(e);
238 return true;
239 }
240 });
241 }
242
243 @Override
244 public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
245 onTouchEvent(e);
246 return false;
247 }
248
249 @Override
250 public void onTouchEvent(RecyclerView rv, MotionEvent e) {
251 onTouchEvent(e);
252 }
253
254 @Override
255 public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
256 }
257 }
258}