2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.localepicker;
19 import android.content.Context;
20 import android.graphics.Canvas;
21 import android.os.Bundle;
22 import android.support.v4.view.MotionEventCompat;
23 import android.support.v7.widget.RecyclerView;
24 import android.support.v7.widget.helper.ItemTouchHelper;
25 import android.util.LocaleList;
26 import android.util.Log;
27 import android.util.TypedValue;
28 import android.view.LayoutInflater;
29 import android.view.MotionEvent;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.CompoundButton;
34 import com.android.internal.app.LocalePicker;
35 import com.android.internal.app.LocaleStore;
37 import com.android.settings.R;
39 import java.text.NumberFormat;
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Locale;
45 class LocaleDragAndDropAdapter
46 extends RecyclerView.Adapter<LocaleDragAndDropAdapter.CustomViewHolder> {
48 private static final String TAG = "LocaleDragAndDropAdapter";
49 private static final String CFGKEY_SELECTED_LOCALES = "selectedLocales";
50 private final Context mContext;
51 private final List<LocaleStore.LocaleInfo> mFeedItemList;
52 private final ItemTouchHelper mItemTouchHelper;
53 private RecyclerView mParentView = null;
54 private boolean mRemoveMode = false;
55 private boolean mDragEnabled = true;
56 private NumberFormat mNumberFormatter = NumberFormat.getNumberInstance();
58 class CustomViewHolder extends RecyclerView.ViewHolder implements View.OnTouchListener {
59 private final LocaleDragCell mLocaleDragCell;
61 public CustomViewHolder(LocaleDragCell view) {
63 mLocaleDragCell = view;
64 mLocaleDragCell.getDragHandle().setOnTouchListener(this);
67 public LocaleDragCell getLocaleDragCell() {
68 return mLocaleDragCell;
72 public boolean onTouch(View v, MotionEvent event) {
74 switch (MotionEventCompat.getActionMasked(event)) {
75 case MotionEvent.ACTION_DOWN:
76 mItemTouchHelper.startDrag(this);
83 public LocaleDragAndDropAdapter(Context context, List<LocaleStore.LocaleInfo> feedItemList) {
84 this.mFeedItemList = feedItemList;
86 this.mContext = context;
88 final float dragElevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
89 context.getResources().getDisplayMetrics());
91 this.mItemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
92 ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0 /* no swipe */) {
95 public boolean onMove(RecyclerView view, RecyclerView.ViewHolder source,
96 RecyclerView.ViewHolder target) {
97 onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
102 public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
103 // Swipe is disabled, this is intentionally empty.
106 private static final int SELECTION_GAINED = 1;
107 private static final int SELECTION_LOST = 0;
108 private static final int SELECTION_UNCHANGED = -1;
109 private int mSelectionStatus = SELECTION_UNCHANGED;
112 public void onChildDraw(Canvas c, RecyclerView recyclerView,
113 RecyclerView.ViewHolder viewHolder, float dX, float dY,
114 int actionState, boolean isCurrentlyActive) {
116 super.onChildDraw(c, recyclerView, viewHolder, dX, dY,
117 actionState, isCurrentlyActive);
118 // We change the elevation if selection changed
119 if (mSelectionStatus != SELECTION_UNCHANGED) {
120 viewHolder.itemView.setElevation(
121 mSelectionStatus == SELECTION_GAINED ? dragElevation : 0);
122 mSelectionStatus = SELECTION_UNCHANGED;
127 public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
128 super.onSelectedChanged(viewHolder, actionState);
129 if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
130 mSelectionStatus = SELECTION_GAINED;
131 } else if (actionState == ItemTouchHelper.ACTION_STATE_IDLE) {
132 mSelectionStatus = SELECTION_LOST;
138 public void setRecyclerView(RecyclerView rv) {
140 mItemTouchHelper.attachToRecyclerView(rv);
144 public CustomViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
145 final LocaleDragCell item = (LocaleDragCell) LayoutInflater.from(mContext)
146 .inflate(R.layout.locale_drag_cell, viewGroup, false);
147 return new CustomViewHolder(item);
151 public void onBindViewHolder(final CustomViewHolder holder, int i) {
152 final LocaleStore.LocaleInfo feedItem = mFeedItemList.get(i);
153 final LocaleDragCell dragCell = holder.getLocaleDragCell();
154 final String label = feedItem.getFullNameNative();
155 final String description = feedItem.getFullNameInUiLanguage();
156 dragCell.setLabelAndDescription(label, description);
157 dragCell.setLocalized(feedItem.isTranslated());
158 dragCell.setMiniLabel(mNumberFormatter.format(i + 1));
159 dragCell.setShowCheckbox(mRemoveMode);
160 dragCell.setShowMiniLabel(!mRemoveMode);
161 dragCell.setShowHandle(!mRemoveMode && mDragEnabled);
162 dragCell.setChecked(mRemoveMode ? feedItem.getChecked() : false);
163 dragCell.setTag(feedItem);
164 dragCell.getCheckbox()
165 .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
167 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
168 LocaleStore.LocaleInfo feedItem =
169 (LocaleStore.LocaleInfo) dragCell.getTag();
170 feedItem.setChecked(isChecked);
176 public int getItemCount() {
177 int itemCount = (null != mFeedItemList ? mFeedItemList.size() : 0);
178 if (itemCount < 2 || mRemoveMode) {
179 setDragEnabled(false);
181 setDragEnabled(true);
186 void onItemMove(int fromPosition, int toPosition) {
187 if (fromPosition >= 0 && toPosition >= 0) {
188 final LocaleStore.LocaleInfo saved = mFeedItemList.get(fromPosition);
189 mFeedItemList.remove(fromPosition);
190 mFeedItemList.add(toPosition, saved);
192 // TODO: It looks like sometimes the RecycleView tries to swap item -1
193 // I did not see it in a while, but if it happens, investigate and file a bug.
194 Log.e(TAG, String.format(Locale.US,
195 "Negative position in onItemMove %d -> %d", fromPosition, toPosition));
197 notifyItemChanged(fromPosition); // to update the numbers
198 notifyItemChanged(toPosition);
199 notifyItemMoved(fromPosition, toPosition);
200 // We don't call doTheUpdate() here because this method is called for each item swap.
201 // So if we drag something across several positions it will be called several times.
204 void setRemoveMode(boolean removeMode) {
205 mRemoveMode = removeMode;
206 int itemCount = mFeedItemList.size();
207 for (int i = 0; i < itemCount; i++) {
208 mFeedItemList.get(i).setChecked(false);
209 notifyItemChanged(i);
213 boolean isRemoveMode() {
217 void removeItem(int position) {
218 int itemCount = mFeedItemList.size();
219 if (itemCount <= 1) {
222 if (position < 0 || position >= itemCount) {
225 mFeedItemList.remove(position);
226 notifyDataSetChanged();
229 void removeChecked() {
230 int itemCount = mFeedItemList.size();
231 for (int i = itemCount - 1; i >= 0; i--) {
232 if (mFeedItemList.get(i).getChecked()) {
233 mFeedItemList.remove(i);
236 notifyDataSetChanged();
240 int getCheckedCount() {
242 for (LocaleStore.LocaleInfo li : mFeedItemList) {
243 if (li.getChecked()) {
250 LocaleStore.LocaleInfo getFirstChecked() {
251 for (LocaleStore.LocaleInfo li : mFeedItemList) {
252 if (li.getChecked()) {
259 void addLocale(LocaleStore.LocaleInfo li) {
260 mFeedItemList.add(li);
261 notifyItemInserted(mFeedItemList.size() - 1);
265 public void doTheUpdate() {
266 int count = mFeedItemList.size();
267 final Locale[] newList = new Locale[count];
269 for (int i = 0; i < count; i++) {
270 final LocaleStore.LocaleInfo li = mFeedItemList.get(i);
271 newList[i] = li.getLocale();
274 final LocaleList ll = new LocaleList(newList);
275 updateLocalesWhenAnimationStops(ll);
278 private LocaleList mLocalesToSetNext = null;
279 private LocaleList mLocalesSetLast = null;
281 public void updateLocalesWhenAnimationStops(final LocaleList localeList) {
282 if (localeList.equals(mLocalesToSetNext)) {
286 // This will only update the Settings application to make things feel more responsive,
287 // the system will be updated later, when animation stopped.
288 LocaleList.setDefault(localeList);
290 mLocalesToSetNext = localeList;
291 final RecyclerView.ItemAnimator itemAnimator = mParentView.getItemAnimator();
292 itemAnimator.isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
294 public void onAnimationsFinished() {
295 if (mLocalesToSetNext == null || mLocalesToSetNext.equals(mLocalesSetLast)) {
296 // All animations finished, but the locale list did not change
300 LocalePicker.updateLocales(mLocalesToSetNext);
301 mLocalesSetLast = mLocalesToSetNext;
302 mLocalesToSetNext = null;
304 mNumberFormatter = NumberFormat.getNumberInstance(Locale.getDefault());
309 private void setDragEnabled(boolean enabled) {
310 mDragEnabled = enabled;
314 * Saves the list of checked locales to preserve status when the list is destroyed.
315 * (for instance when the device is rotated)
316 * @param outInstanceState Bundle in which to place the saved state
318 public void saveState(Bundle outInstanceState) {
319 if (outInstanceState != null) {
320 final ArrayList<String> selectedLocales = new ArrayList<>();
321 for (LocaleStore.LocaleInfo li : mFeedItemList) {
322 if (li.getChecked()) {
323 selectedLocales.add(li.getId());
326 outInstanceState.putStringArrayList(CFGKEY_SELECTED_LOCALES, selectedLocales);
331 * Restores the list of checked locales to preserve status when the list is recreated.
332 * (for instance when the device is rotated)
333 * @param savedInstanceState Bundle with the data saved by {@link #saveState(Bundle)}
335 public void restoreState(Bundle savedInstanceState) {
336 if (savedInstanceState != null && mRemoveMode) {
337 final ArrayList<String> selectedLocales =
338 savedInstanceState.getStringArrayList(CFGKEY_SELECTED_LOCALES);
339 if (selectedLocales == null || selectedLocales.isEmpty()) {
342 for (LocaleStore.LocaleInfo li : mFeedItemList) {
343 li.setChecked(selectedLocales.contains(li.getId()));
345 notifyItemRangeChanged(0, mFeedItemList.size());