[2/2] OmniGears: lock screen shortcuts
custom shortcut apps on lockscreen
-config if camera and or dialer shortcut should be shown
-config to hide the informative text (custom shortcuts will
always hide the text if expanded)
Change-Id: I12f50a646725fa8bf626ce512ec1af3dd56a54fd
diff --git a/res/layout/app_item.xml b/res/layout/app_item.xml
new file mode 100644
index 0000000..6b3435f
--- /dev/null
+++ b/res/layout/app_item.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The OmniROM Project
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingTop="8dip"
+ android:paddingBottom="8dip"
+ android:orientation="horizontal" >
+
+ <ImageView
+ android:id="@+id/app_icon"
+ android:layout_width="@android:dimen/app_icon_size"
+ android:layout_height="@android:dimen/app_icon_size"
+ android:layout_marginEnd="8dip"
+ android:layout_gravity="center_vertical"
+ android:scaleType="centerInside"
+ android:contentDescription="@null" />
+
+ <TextView
+ android:id="@+id/app_name"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:focusable="false"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textAlignment="viewStart" />
+
+</LinearLayout>
+
diff --git a/res/layout/shortcut_app_item.xml b/res/layout/shortcut_app_item.xml
new file mode 100644
index 0000000..bbe0e29
--- /dev/null
+++ b/res/layout/shortcut_app_item.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2013 The OmniROM Project
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+-->
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:columnCount="3"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart" >
+
+ <org.omnirom.omnigears.ui.dslv.DragGripView
+ android:id="@+id/drag_handle"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical"
+ android:paddingBottom="12dp"
+ android:paddingTop="12dp" />
+
+ <ImageView
+ android:id="@+id/app_icon"
+ android:layout_width="@android:dimen/app_icon_size"
+ android:layout_height="@android:dimen/app_icon_size"
+ android:layout_marginEnd="8dip"
+ android:layout_gravity="center_vertical"
+ android:scaleType="centerInside"
+ android:contentDescription="@null" />
+
+ <TextView
+ android:id="@+id/app_name"
+ android:layout_width="0dip"
+ android:layout_gravity="fill_horizontal|center_vertical"
+ android:ellipsize="end"
+ android:focusable="false"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="?android:attr/textAppearanceListItem" />
+</GridLayout>
diff --git a/res/layout/shortcut_dialog.xml b/res/layout/shortcut_dialog.xml
new file mode 100644
index 0000000..e91de85
--- /dev/null
+++ b/res/layout/shortcut_dialog.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The OmniROM Project
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/alert_dialog_padding_material"
+ android:paddingEnd="@dimen/alert_dialog_padding_material" >
+ <org.omnirom.omnigears.ui.dslv.DragSortListView
+ android:id="@+id/shortcut_apps"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:dividerHeight="1dp" />
+</RelativeLayout>
diff --git a/res/values/custom_dimens.xml b/res/values/custom_dimens.xml
index beb265a..57af2c0 100755
--- a/res/values/custom_dimens.xml
+++ b/res/values/custom_dimens.xml
@@ -19,4 +19,6 @@
<dimen name="alert_dialog_padding_material">20dp</dimen>
<dimen name="color_preference_width">18dip</dimen>
<dimen name="color_preference_height">18dip</dimen>
+ <dimen name="drag_grip_ridge_size">6dp</dimen>
+ <dimen name="drag_grip_ridge_gap">4dp</dimen>
</resources>
diff --git a/res/values/custom_strings.xml b/res/values/custom_strings.xml
index a28c032..64b9bd2 100644
--- a/res/values/custom_strings.xml
+++ b/res/values/custom_strings.xml
@@ -212,7 +212,7 @@
<!-- Lock screen config -->
<string name="lockscreen_shortcut_title">Shortcuts</string>
<string name="lockscreen_voice_shortcut_title">Voice assist shortcut</string>
- <string name="lockscreen_voice_shortcut_summary">Show voice assist instead of dialer shortcut</string>
+ <string name="lockscreen_voice_shortcut_summary">Show voice assist instead of dialer</string>
<string name="lockscreen_shortcuts_enable_title">Enable</string>
<string name="lockscreen_clock_display_time_title">Display time</string>
<string name="lockscreen_clock_display_date_title">Display date</string>
@@ -223,6 +223,17 @@
<string name="lockscreen_clock_size_title">Time size</string>
<string name="lockscreen_clock_enable_title">Enable</string>
<string name="lockscreen_clock_shadow_title">Shadow</string>
+ <string name="lockscreen_shortcuts_title">Custom application shortcuts</string>
+ <string name="lockscreen_shortcuts_summary">Applications shown on the lock screen for quick access. Long press to hide.</string>
+ <string name="lockscreen_shortcut_add_dialog_title">Add shortcut</string>
+ <string name="lockscreen_shortcut_apps_title">Shortcuts</string>
+ <string name="lockscreen_shortcut_add">Add\u2026</string>
+ <string name="lockscreen_indicator_display_title">Show bottom status text</string>
+ <string name="lockscreen_indicator_display_summary">Note: custom shortcuts will hide text</string>
+ <string name="lockscreen_camera_shortcut_enable_title">Show camera shortcut</string>
+ <string name="lockscreen_camera_shortcut_enable_summary">Visibility depends on system setup</string>
+ <string name="lockscreen_left_shortcut_enable_title">Show dialer or voice assist shortcut</string>
+ <string name="lockscreen_left_shortcut_enable_summary">Visibility depends on system setup</string>
<string name="notification_title">Notification panel</string>
<string name="status_bar_custom_header_title">Custom header image</string>
diff --git a/res/values/dslv_attrs.xml b/res/values/dslv_attrs.xml
new file mode 100644
index 0000000..5e706d4
--- /dev/null
+++ b/res/values/dslv_attrs.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!--
+Copyright 2012 Carl Bauer
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<resources>
+ <declare-styleable name="DragSortListView">
+ <attr name="collapsed_height" format="dimension" />
+ <attr name="drag_scroll_start" format="float" />
+ <attr name="max_drag_scroll_speed" format="float" />
+ <attr name="float_background_color" format="color" />
+ <attr name="remove_mode">
+ <enum name="clickRemove" value="0" />
+ <enum name="flingRight" value="1" />
+ <enum name="flingLeft" value="2" />
+ <enum name="slideRight" value="3" />
+ <enum name="slideLeft" value="4" />
+ </attr>
+ <attr name="track_drag_sort" format="boolean" />
+ <attr name="float_alpha" format="float" />
+ <attr name="slide_shuffle_speed" format="float" />
+ <attr name="remove_animation_duration" format="integer" />
+ <attr name="drop_animation_duration" format="integer" />
+ <attr name="drag_enabled" format="boolean" />
+ <attr name="sort_enabled" format="boolean" />
+ <attr name="remove_enabled" format="boolean" />
+ <attr name="drag_start_mode">
+ <enum name="onDown" value="0" />
+ <enum name="onMove" value="1" />
+ <enum name="onLongPress" value="2" />
+ </attr>
+ <attr name="drag_handle_id" format="integer" />
+ <attr name="click_remove_id" format="integer" />
+ <attr name="use_default_controller" format="boolean" />
+ </declare-styleable>
+</resources>
diff --git a/res/xml/lockscreen_settings.xml b/res/xml/lockscreen_settings.xml
index f639958..0cd4074 100644
--- a/res/xml/lockscreen_settings.xml
+++ b/res/xml/lockscreen_settings.xml
@@ -27,12 +27,43 @@
android:defaultValue="true"/>
<com.android.settings.preference.SecureCheckBoxPreference
+ android:key="lockscreen_camera_shortcut_enable"
+ android:title="@string/lockscreen_camera_shortcut_enable_title"
+ android:summary="@string/lockscreen_camera_shortcut_enable_summary"
+ android:defaultValue="true"
+ android:dependency="lockscreen_shortcuts_enable" />
+
+ <com.android.settings.preference.SecureCheckBoxPreference
+ android:key="lockscreen_left_shortcut_enable"
+ android:title="@string/lockscreen_left_shortcut_enable_title"
+ android:summary="@string/lockscreen_left_shortcut_enable_summary"
+ android:defaultValue="true"
+ android:dependency="lockscreen_shortcuts_enable" />
+
+ <com.android.settings.preference.SecureCheckBoxPreference
android:key="lockscreen_voice_shortcut"
android:title="@string/lockscreen_voice_shortcut_title"
android:summary="@string/lockscreen_voice_shortcut_summary"
android:defaultValue="true"
- android:dependency="lockscreen_shortcuts_enable" />
+ android:dependency="lockscreen_left_shortcut_enable" />
+ <Preference
+ android:key="lockscreen_shortcuts"
+ android:title="@string/lockscreen_shortcuts_title"
+ android:summary="@string/lockscreen_shortcuts_summary"
+ android:persistent="false"
+ android:dependency="lockscreen_shortcuts_enable" />
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="lockscreen_other"
+ android:title="@string/other_category" >
+
+ <com.android.settings.preference.SystemSettingSwitchPreference
+ android:key="lockscreen_indicator_display"
+ android:title="@string/lockscreen_indicator_display_title"
+ android:summary="@string/lockscreen_indicator_display_summary"
+ android:defaultValue="true" />
</PreferenceCategory>
<PreferenceCategory
@@ -106,4 +137,5 @@
android:dependency="lockscreen_clock_enable" />
</PreferenceCategory>
+
</PreferenceScreen>
diff --git a/src/org/omnirom/omnigears/interfacesettings/LockscreenSettings.java b/src/org/omnirom/omnigears/interfacesettings/LockscreenSettings.java
index 2bd3ceb..628f94c 100644
--- a/src/org/omnirom/omnigears/interfacesettings/LockscreenSettings.java
+++ b/src/org/omnirom/omnigears/interfacesettings/LockscreenSettings.java
@@ -32,6 +32,7 @@
import android.preference.SwitchPreference;
import android.provider.Settings;
import android.provider.SearchIndexableResource;
+import android.text.TextUtils;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
@@ -40,12 +41,17 @@
import com.android.settings.search.Indexable;
import org.omnirom.omnigears.R;
+import org.omnirom.omnigears.preference.AppMultiSelectListPreference;
import org.omnirom.omnigears.preference.FontPreference;
import org.omnirom.omnigears.preference.NumberPickerPreference;
import org.omnirom.omnigears.preference.ColorPickerPreference;
+import org.omnirom.omnigears.ui.ShortcutDialog;
-import java.util.List;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
public class LockscreenSettings extends SettingsPreferenceFragment implements
Preference.OnPreferenceChangeListener, Indexable {
@@ -60,6 +66,7 @@
private static final String KEY_CLOCK_DISPLAY_TIME = "lockscreen_clock_display_time";
private static final String KEY_CLOCK_DISPLAY_DATE = "lockscreen_clock_display_date";
private static final String KEY_CLOCK_DISPLAY_ALARM = "lockscreen_clock_display_alarm";
+ private static final String KEY_SHORTCUTS = "lockscreen_shortcuts";
private Preference mSetWallpaper;
private Preference mClearWallpaper;
@@ -69,6 +76,7 @@
private CheckBoxPreference mClockDisplayTime;
private CheckBoxPreference mClockDisplayDate;
private CheckBoxPreference mClockDisplayAlarm;
+ private Preference mShortcuts;
@Override
protected int getMetricsCategory() {
@@ -123,6 +131,8 @@
mClockDisplayTime.setChecked((clockDisplay & Settings.System.LOCK_CLOCK_TIME) == Settings.System.LOCK_CLOCK_TIME);
mClockDisplayDate.setChecked((clockDisplay & Settings.System.LOCK_CLOCK_DATE) == Settings.System.LOCK_CLOCK_DATE);
mClockDisplayAlarm.setChecked((clockDisplay & Settings.System.LOCK_CLOCK_ALARM) == Settings.System.LOCK_CLOCK_ALARM);
+
+ mShortcuts = findPreference(KEY_SHORTCUTS);
}
@Override
@@ -148,6 +158,10 @@
Settings.System.putInt(resolver,
Settings.System.LOCK_CLOCK_DISPLAY, getCurrentClockDisplayValue());
return true;
+ } else if (preference == mShortcuts) {
+ ShortcutDialog d = new ShortcutDialog(getContext());
+ d.show();
+ return true;
}
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
diff --git a/src/org/omnirom/omnigears/preference/AppMultiSelectListPreference.java b/src/org/omnirom/omnigears/preference/AppMultiSelectListPreference.java
index 90efb74..545a25e 100644
--- a/src/org/omnirom/omnigears/preference/AppMultiSelectListPreference.java
+++ b/src/org/omnirom/omnigears/preference/AppMultiSelectListPreference.java
@@ -44,6 +44,7 @@
import java.text.Collator;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
@@ -78,12 +79,12 @@
Collections.sort(mPackageInfoList, sDisplayNameComparator);
}
- public void setValues(Set<String> values) {
+ public void setValues(Collection<String> values) {
mValues.clear();
mValues.addAll(values);
}
- public Set<String> getValues() {
+ public Collection<String> getValues() {
return mValues;
}
diff --git a/src/org/omnirom/omnigears/ui/ShortcutDialog.java b/src/org/omnirom/omnigears/ui/ShortcutDialog.java
new file mode 100644
index 0000000..3dc7726
--- /dev/null
+++ b/src/org/omnirom/omnigears/ui/ShortcutDialog.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2016 The OmniROM Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.omnirom.omnigears.ui;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import org.omnirom.omnigears.R;
+import org.omnirom.omnigears.ui.dslv.DragSortController;
+import org.omnirom.omnigears.ui.dslv.DragSortListView;
+
+import java.net.URISyntaxException;
+import java.text.Collator;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+public class ShortcutDialog extends AlertDialog implements
+ DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+ private LayoutInflater mInflater;
+ private List<String> mShortcutList;
+ private ShortcutListAdapter mShortcutAdapter;
+ private DragSortListView mShortcutConfigList;
+ private AlertDialog mAddShortcutDialog;
+ private final List<MyApplicationInfo> mPackageInfoList = new ArrayList<MyApplicationInfo>();
+
+ ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ class MyApplicationInfo {
+ ApplicationInfo info;
+ CharSequence label;
+ ResolveInfo resolveInfo;
+ }
+
+ public class ShortcutListAdapter extends ArrayAdapter<String> {
+ public ShortcutListAdapter(Context context) {
+ super(context, R.layout.shortcut_app_item, mShortcutList);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ AppViewHolder holder = AppViewHolder.createOrRecycle(mInflater, convertView);
+ convertView = holder.rootView;
+ String intentString = getItem(position);
+ Intent intent = null;
+ try {
+ intent = Intent.parseUri(intentString, 0);
+ } catch (URISyntaxException e) {
+ // TODO should never happen - better build a intent list
+ return convertView;
+ }
+ final List<ResolveInfo> pkgAppsList = getContext().getPackageManager().queryIntentActivities(intent, 0);
+ if (pkgAppsList.size() > 0) {
+ Drawable icon = pkgAppsList.get(0).activityInfo.loadIcon(getContext().getPackageManager());
+ CharSequence label = pkgAppsList.get(0).activityInfo.loadLabel(getContext().getPackageManager());
+ holder.appName.setText(label);
+ holder.appIcon.setImageDrawable(icon);
+ }
+ return convertView;
+ }
+ }
+
+ private class ShortcutDragSortController extends DragSortController {
+
+ public ShortcutDragSortController() {
+ super(mShortcutConfigList, R.id.drag_handle,
+ DragSortController.ON_DOWN,
+ DragSortController.FLING_RIGHT_REMOVE);
+ setRemoveEnabled(true);
+ setSortEnabled(true);
+ setBackgroundColor(0x363636);
+ }
+
+ @Override
+ public void onDragFloatView(View floatView, Point floatPoint,
+ Point touchPoint) {
+ floatView.setLayoutParams(params);
+ mShortcutConfigList.setFloatAlpha(0.8f);
+ }
+
+ @Override
+ public View onCreateFloatView(int position) {
+ View v = mShortcutAdapter.getView(position, null,
+ mShortcutConfigList);
+ v.setLayoutParams(params);
+ return v;
+ }
+
+ @Override
+ public void onDestroyFloatView(View floatView) {
+ }
+ }
+
+ public ShortcutDialog(Context context) {
+ super(context);
+ mInflater = (LayoutInflater) context
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ List<ResolveInfo> installedAppsInfo = getContext().getPackageManager().queryIntentActivities(
+ mainIntent, 0);
+
+ for (ResolveInfo info : installedAppsInfo) {
+ MyApplicationInfo myInfo = new MyApplicationInfo();
+ myInfo.resolveInfo = info;
+ myInfo.label = getResolveInfoTitle(info);
+ mPackageInfoList.add(myInfo);
+ }
+ Collections.sort(mPackageInfoList, sDisplayNameComparator);
+
+ mShortcutList = new ArrayList<String>();
+ String shortcutStrings = Settings.Secure.getString(getContext().getContentResolver(), Settings.Secure.LOCK_SHORTCUTS);
+ if (shortcutStrings != null && shortcutStrings.length() != 0) {
+ String[] values = TextUtils.split(shortcutStrings, "##");
+
+ for (String intentString : values) {
+ Intent intent = null;
+ try {
+ intent = Intent.parseUri(intentString, 0);
+ } catch (URISyntaxException e) {
+ continue;
+ }
+
+ final List<ResolveInfo> pkgAppsList = getContext().getPackageManager().queryIntentActivities(intent, 0);
+ if (pkgAppsList.size() == 0) {
+ continue;
+ }
+ mShortcutList.add(intentString);
+ }
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ final Context context = getContext();
+ final View view = getLayoutInflater().inflate(R.layout.shortcut_dialog,
+ null);
+ setView(view);
+ setTitle(R.string.lockscreen_shortcut_apps_title);
+ setCancelable(true);
+
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(android.R.string.ok), this);
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(R.string.lockscreen_shortcut_add), this);
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getString(android.R.string.cancel), this);
+
+ super.onCreate(savedInstanceState);
+
+ mShortcutConfigList = (DragSortListView) view
+ .findViewById(R.id.shortcut_apps);
+ mShortcutAdapter = new ShortcutListAdapter(context);
+ mShortcutConfigList.setAdapter(mShortcutAdapter);
+
+ final DragSortController dragSortController = new ShortcutDragSortController();
+ mShortcutConfigList.setFloatViewManager(dragSortController);
+ mShortcutConfigList
+ .setDropListener(new DragSortListView.DropListener() {
+ @Override
+ public void drop(int from, int to) {
+ String intent = mShortcutList.remove(from);
+ mShortcutList.add(to, intent);
+ mShortcutAdapter.notifyDataSetChanged();
+ }
+ });
+ mShortcutConfigList
+ .setRemoveListener(new DragSortListView.RemoveListener() {
+ @Override
+ public void remove(int which) {
+ mShortcutList.remove(which);
+ mShortcutAdapter.notifyDataSetChanged();
+ }
+ });
+ mShortcutConfigList.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+ return dragSortController.onTouch(view, motionEvent);
+ }
+ });
+ mShortcutConfigList.setItemsCanFocus(false);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ Button neutralButton = getButton(DialogInterface.BUTTON_NEUTRAL);
+ neutralButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showAddShortcutDialog();
+ }
+ });
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mAddShortcutDialog != null) {
+ mAddShortcutDialog.dismiss();
+ }
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ if (mAddShortcutDialog != null) {
+ mAddShortcutDialog = null;
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ Settings.Secure.putString(getContext().getContentResolver(), Settings.Secure.LOCK_SHORTCUTS,
+ TextUtils.join("##", mShortcutList.toArray()));
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ cancel();
+ }
+ }
+
+ private String getResolveInfoTitle(ResolveInfo info) {
+ CharSequence label = info.loadLabel(getContext().getPackageManager());
+ if (label == null) label = info.activityInfo.name;
+ return label != null ? label.toString() : null;
+ }
+
+ private Intent getIntentForResolveInfo(ResolveInfo info, String action) {
+ Intent intent = new Intent(action);
+ ActivityInfo ai = info.activityInfo;
+ intent.setClassName(ai.packageName, ai.name);
+ return intent;
+ }
+
+ private void showAddShortcutDialog() {
+ if (mAddShortcutDialog != null && mAddShortcutDialog.isShowing()) {
+ return;
+ }
+
+ mAddShortcutDialog = new AddShortcutDialog(getContext());
+ mAddShortcutDialog.setOnDismissListener(this);
+ mAddShortcutDialog.show();
+ }
+
+ public void applyChanges(List<String> shortcutListEdit) {
+ mShortcutList.clear();
+ mShortcutList.addAll(shortcutListEdit);
+ mShortcutAdapter.notifyDataSetChanged();
+ }
+
+ private class AddShortcutDialog extends AlertDialog implements
+ DialogInterface.OnClickListener {
+ private AppListAdapter mAppAdapter;
+ private List<String> mShortcutListEdit;
+ private ListView mListView;
+
+ public class AppListAdapter extends ArrayAdapter<MyApplicationInfo> {
+ public AppListAdapter(Context context) {
+ super(context, R.layout.app_select_item, mPackageInfoList);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ AppSelectViewHolder holder = AppSelectViewHolder.createOrRecycle(mInflater, convertView);
+ convertView = holder.rootView;
+ MyApplicationInfo info = getItem(position);
+ holder.appName.setText(info.label);
+ Drawable icon = info.resolveInfo.loadIcon(getContext().getPackageManager());
+ if (icon != null) {
+ holder.appIcon.setImageDrawable(icon);
+ } else {
+ holder.appIcon.setImageDrawable(null);
+ }
+
+ Intent intent = getIntentForResolveInfo(info.resolveInfo, Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ String value = intent.toUri(0).toString();
+ holder.checkBox.setChecked(mShortcutListEdit.contains(value));
+ return convertView;
+ }
+ }
+
+ protected AddShortcutDialog(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ applyChanges(mShortcutListEdit);
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ cancel();
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ final Context context = getContext();
+ final View view = getLayoutInflater().inflate(
+ R.layout.preference_app_list, null);
+ setView(view);
+ setTitle(R.string.lockscreen_shortcut_add_dialog_title);
+ setCancelable(true);
+
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(android.R.string.ok), this);
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getString(android.R.string.cancel), this);
+
+ super.onCreate(savedInstanceState);
+ mShortcutListEdit = new ArrayList<String>();
+ mShortcutListEdit.addAll(mShortcutList);
+ mListView = (ListView) view.findViewById(R.id.app_list);
+ mAppAdapter = new AppListAdapter(getContext());
+ mListView.setAdapter(mAppAdapter);
+ mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ final AppSelectViewHolder holder = (AppSelectViewHolder) view.getTag();
+ final boolean isChecked = !holder.checkBox.isChecked();
+ holder.checkBox.setChecked(isChecked);
+
+ MyApplicationInfo myInfo = mAppAdapter.getItem(position);
+ ResolveInfo info = myInfo.resolveInfo;
+ Intent intent = getIntentForResolveInfo(info, Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ String value = intent.toUri(0).toString();
+
+ if (isChecked) {
+ mShortcutListEdit.add(value);
+ } else {
+ mShortcutListEdit.remove(value);
+ }
+ }
+ });
+ }
+ }
+
+ public static class AppSelectViewHolder {
+ public View rootView;
+ public TextView appName;
+ public ImageView appIcon;
+ public CheckBox checkBox;
+
+ public static AppSelectViewHolder createOrRecycle(LayoutInflater inflater, View convertView) {
+ if (convertView == null) {
+ convertView = inflater.inflate(R.layout.app_select_item, null);
+
+ // Creates a ViewHolder and store references to the two children views
+ // we want to bind data to.
+ AppSelectViewHolder holder = new AppSelectViewHolder();
+ holder.rootView = convertView;
+ holder.appName = (TextView) convertView.findViewById(R.id.app_name);
+ holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
+ holder.checkBox = (CheckBox) convertView.findViewById(android.R.id.checkbox);
+ convertView.setTag(holder);
+ return holder;
+ } else {
+ // Get the ViewHolder back to get fast access to the TextView
+ // and the ImageView.
+ return (AppSelectViewHolder)convertView.getTag();
+ }
+ }
+ }
+
+ public static class AppViewHolder {
+ public View rootView;
+ public TextView appName;
+ public ImageView appIcon;
+
+ public static AppViewHolder createOrRecycle(LayoutInflater inflater, View convertView) {
+ if (convertView == null) {
+ convertView = inflater.inflate(R.layout.shortcut_app_item, null);
+
+ // Creates a ViewHolder and store references to the two children views
+ // we want to bind data to.
+ AppViewHolder holder = new AppViewHolder();
+ holder.rootView = convertView;
+ holder.appName = (TextView) convertView.findViewById(R.id.app_name);
+ holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
+ convertView.setTag(holder);
+ return holder;
+ } else {
+ // Get the ViewHolder back to get fast access to the TextView
+ // and the ImageView.
+ return (AppViewHolder)convertView.getTag();
+ }
+ }
+ }
+
+ private final static Comparator<MyApplicationInfo> sDisplayNameComparator
+ = new Comparator<MyApplicationInfo>() {
+
+ private final Collator collator = Collator.getInstance();
+
+ public final int compare(MyApplicationInfo a, MyApplicationInfo b) {
+ return collator.compare(a.label, b.label);
+ }
+ };
+}
diff --git a/src/org/omnirom/omnigears/ui/dslv/DragGripView.java b/src/org/omnirom/omnigears/ui/dslv/DragGripView.java
new file mode 100644
index 0000000..8922035
--- /dev/null
+++ b/src/org/omnirom/omnigears/ui/dslv/DragGripView.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.omnigears.ui.dslv;
+
+import org.omnirom.omnigears.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+
+public class DragGripView extends View {
+ private static final int[] ATTRS = new int[]{
+ android.R.attr.gravity,
+ android.R.attr.color,
+ };
+
+ private static final int HORIZ_RIDGES = 3;
+
+ private int mGravity = Gravity.START;
+ private int mColor = Color.BLACK;
+
+ private Paint mRidgePaint;
+
+ private float mRidgeSize;
+ private float mRidgeGap;
+
+ private int mWidth;
+ private int mHeight;
+
+ public DragGripView(Context context) {
+ this(context, null, 0);
+ }
+
+ public DragGripView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DragGripView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
+ mGravity = a.getInteger(0, mGravity);
+ mColor = a.getColor(1, mColor);
+ a.recycle();
+
+ TypedValue value = new TypedValue();
+ getContext().getTheme().resolveAttribute(android.R.attr.colorControlNormal, value, true);
+ mColor = getContext().getColor(value.resourceId);
+
+ final Resources res = getResources();
+ mRidgeSize = res.getDimensionPixelSize(R.dimen.drag_grip_ridge_size);
+ mRidgeGap = res.getDimensionPixelSize(R.dimen.drag_grip_ridge_gap);
+
+ mRidgePaint = new Paint();
+ mRidgePaint.setColor(mColor);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(
+ View.resolveSize(
+ (int) (HORIZ_RIDGES * (mRidgeSize + mRidgeGap) - mRidgeGap)
+ + getPaddingLeft() + getPaddingRight(),
+ widthMeasureSpec),
+ View.resolveSize(
+ (int) mRidgeSize,
+ heightMeasureSpec));
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ float drawWidth = HORIZ_RIDGES * (mRidgeSize + mRidgeGap) - mRidgeGap;
+ float drawLeft;
+
+ switch (Gravity.getAbsoluteGravity(mGravity, getLayoutDirection())
+ & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ drawLeft = getPaddingLeft()
+ + ((mWidth - getPaddingLeft() - getPaddingRight()) - drawWidth) / 2;
+ break;
+ case Gravity.RIGHT:
+ drawLeft = getWidth() - getPaddingRight() - drawWidth;
+ break;
+ default:
+ drawLeft = getPaddingLeft();
+ }
+
+ int vertRidges = (int) ((mHeight - getPaddingTop() - getPaddingBottom() + mRidgeGap)
+ / (mRidgeSize + mRidgeGap));
+ float drawHeight = vertRidges * (mRidgeSize + mRidgeGap) - mRidgeGap;
+ float drawTop = getPaddingTop()
+ + ((mHeight - getPaddingTop() - getPaddingBottom()) - drawHeight) / 2;
+
+ for (int y = 0; y < vertRidges; y++) {
+ for (int x = 0; x < HORIZ_RIDGES; x++) {
+ canvas.drawRect(
+ drawLeft + x * (mRidgeSize + mRidgeGap),
+ drawTop + y * (mRidgeSize + mRidgeGap),
+ drawLeft + x * (mRidgeSize + mRidgeGap) + mRidgeSize,
+ drawTop + y * (mRidgeSize + mRidgeGap) + mRidgeSize,
+ mRidgePaint);
+ }
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mHeight = h;
+ mWidth = w;
+ }
+}
diff --git a/src/org/omnirom/omnigears/ui/dslv/DragSortController.java b/src/org/omnirom/omnigears/ui/dslv/DragSortController.java
new file mode 100644
index 0000000..a456923
--- /dev/null
+++ b/src/org/omnirom/omnigears/ui/dslv/DragSortController.java
@@ -0,0 +1,489 @@
+/**
+ * A subclass of the Android ListView component that enables drag
+ * and drop re-ordering of list items.
+ *
+ * Copyright 2012 Carl Bauer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.omnigears.ui.dslv;
+
+import android.graphics.Point;
+import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.AdapterView;
+
+/**
+ * Class that starts and stops item drags on a {@link DragSortListView}
+ * based on touch gestures. This class also inherits from
+ * {@link SimpleFloatViewManager}, which provides basic float View
+ * creation.
+ *
+ * An instance of this class is meant to be passed to the methods
+ * {@link DragSortListView#setTouchListener()} and
+ * {@link DragSortListView#setFloatViewManager()} of your
+ * {@link DragSortListView} instance.
+ */
+public class DragSortController extends SimpleFloatViewManager implements View.OnTouchListener, GestureDetector.OnGestureListener {
+
+ /**
+ * Drag init mode enum.
+ */
+ public static final int ON_DOWN = 0;
+ public static final int ON_DRAG = 1;
+ public static final int ON_LONG_PRESS = 2;
+
+ private int mDragInitMode = ON_DOWN;
+
+ private boolean mSortEnabled = true;
+
+ /**
+ * Remove mode enum.
+ */
+ public static final int CLICK_REMOVE = 0;
+ public static final int FLING_RIGHT_REMOVE = 1;
+ public static final int FLING_LEFT_REMOVE = 2;
+ public static final int SLIDE_RIGHT_REMOVE = 3;
+ public static final int SLIDE_LEFT_REMOVE = 4;
+
+ /**
+ * The current remove mode.
+ */
+ private int mRemoveMode;
+
+ private boolean mRemoveEnabled = false;
+
+ private GestureDetector mDetector;
+
+ private GestureDetector mFlingRemoveDetector;
+
+ private int mTouchSlop;
+
+ public static final int MISS = -1;
+
+ private int mHitPos = MISS;
+
+ private int mClickRemoveHitPos = MISS;
+
+ private int[] mTempLoc = new int[2];
+
+ private int mItemX;
+ private int mItemY;
+
+ private int mCurrX;
+ private int mCurrY;
+
+ private boolean mDragging = false;
+
+ private float mFlingSpeed = 500f;
+
+ private float mOrigFloatAlpha = 1.0f;
+
+ private int mDragHandleId;
+
+ private int mClickRemoveId;
+
+ private DragSortListView mDslv;
+
+
+ /**
+ * Calls {@link #DragSortController(DragSortListView, int)} with a
+ * 0 drag handle id, FLING_RIGHT_REMOVE remove mode,
+ * and ON_DOWN drag init. By default, sorting is enabled, and
+ * removal is disabled.
+ *
+ * @param dslv The DSLV instance
+ */
+ public DragSortController(DragSortListView dslv) {
+ this(dslv, 0, ON_DOWN, FLING_RIGHT_REMOVE);
+ }
+
+ public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode) {
+ this(dslv, dragHandleId, dragInitMode, removeMode, 0);
+ }
+
+ /**
+ * By default, sorting is enabled, and removal is disabled.
+ *
+ * @param dslv The DSLV instance
+ * @param dragHandleId The resource id of the View that represents
+ * the drag handle in a list item.
+ */
+ public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode, int clickRemoveId) {
+ super(dslv);
+ mDslv = dslv;
+ mDetector = new GestureDetector(dslv.getContext(), this);
+ mFlingRemoveDetector = new GestureDetector(dslv.getContext(), mFlingRemoveListener);
+ mFlingRemoveDetector.setIsLongpressEnabled(false);
+ mTouchSlop = ViewConfiguration.get(dslv.getContext()).getScaledTouchSlop();
+ mDragHandleId = dragHandleId;
+ mClickRemoveId = clickRemoveId;
+ setRemoveMode(removeMode);
+ setDragInitMode(dragInitMode);
+ mOrigFloatAlpha = dslv.getFloatAlpha();
+ }
+
+
+ public int getDragInitMode() {
+ return mDragInitMode;
+ }
+
+ /**
+ * Set how a drag is initiated. Needs to be one of
+ * {@link ON_DOWN}, {@link ON_DRAG}, or {@link ON_LONG_PRESS}.
+ *
+ * @param mode The drag init mode.
+ */
+ public void setDragInitMode(int mode) {
+ mDragInitMode = mode;
+ }
+
+ /**
+ * ROMAN'S ADDITION. Returns whether or not a drag is currently occurring.
+ */
+ public boolean isDragging() {
+ return mDragging;
+ }
+
+ /**
+ * Enable/Disable list item sorting. Disabling is useful if only item
+ * removal is desired. Prevents drags in the vertical direction.
+ *
+ * @param enabled Set <code>true</code> to enable list
+ * item sorting.
+ */
+ public void setSortEnabled(boolean enabled) {
+ mSortEnabled = enabled;
+ }
+
+ public boolean isSortEnabled() {
+ return mSortEnabled;
+ }
+
+ /**
+ * One of {@link CLICK_REMOVE}, {@link FLING_RIGHT_REMOVE},
+ * {@link FLING_LEFT_REMOVE},
+ * {@link SLIDE_RIGHT_REMOVE}, or {@link SLIDE_LEFT_REMOVE}.
+ */
+ public void setRemoveMode(int mode) {
+ mRemoveMode = mode;
+ }
+
+ public int getRemoveMode() {
+ return mRemoveMode;
+ }
+
+ /**
+ * Enable/Disable item removal without affecting remove mode.
+ */
+ public void setRemoveEnabled(boolean enabled) {
+ mRemoveEnabled = enabled;
+ }
+
+ public boolean isRemoveEnabled() {
+ return mRemoveEnabled;
+ }
+
+ /**
+ * Set the resource id for the View that represents the drag
+ * handle in a list item.
+ *
+ * @param id An android resource id.
+ */
+ public void setDragHandleId(int id) {
+ mDragHandleId = id;
+ }
+
+ /**
+ * Set the resource id for the View that represents click
+ * removal button.
+ *
+ * @param id An android resource id.
+ */
+ public void setClickRemoveId(int id) {
+ mClickRemoveId = id;
+ }
+
+
+ /**
+ * Sets flags to restrict certain motions of the floating View
+ * based on DragSortController settings (such as remove mode).
+ * Starts the drag on the DragSortListView.
+ *
+ * @param position The list item position (includes headers).
+ * @param deltaX Touch x-coord minus left edge of floating View.
+ * @param deltaY Touch y-coord minus top edge of floating View.
+ *
+ * @return True if drag started, false otherwise.
+ */
+ public boolean startDrag(int position, int deltaX, int deltaY) {
+
+ int dragFlags = 0;
+ if (mSortEnabled) {
+ dragFlags |= DragSortListView.DRAG_POS_Y | DragSortListView.DRAG_NEG_Y;
+ //dragFlags |= DRAG_POS_Y; //for fun
+ }
+ if (mRemoveEnabled) {
+ if (mRemoveMode == FLING_RIGHT_REMOVE) {
+ dragFlags |= DragSortListView.DRAG_POS_X;
+ } else if (mRemoveMode == FLING_LEFT_REMOVE) {
+ dragFlags |= DragSortListView.DRAG_NEG_X;
+ }
+ }
+
+ mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), dragFlags, deltaX, deltaY);
+ return mDragging;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ mDetector.onTouchEvent(ev);
+ if (mRemoveEnabled && mDragging && (mRemoveMode == FLING_RIGHT_REMOVE || mRemoveMode == FLING_LEFT_REMOVE)) {
+ mFlingRemoveDetector.onTouchEvent(ev);
+ }
+
+ int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mCurrX = (int) ev.getX();
+ mCurrY = (int) ev.getY();
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mRemoveEnabled) {
+ final int x = (int) ev.getX();
+ int thirdW = mDslv.getWidth() / 3;
+ int twoThirdW = mDslv.getWidth() - thirdW;
+ if ((mRemoveMode == SLIDE_RIGHT_REMOVE && x > twoThirdW) ||
+ (mRemoveMode == SLIDE_LEFT_REMOVE && x < thirdW)) {
+ mDslv.stopDrag(true);
+ }
+ }
+ case MotionEvent.ACTION_CANCEL:
+ mDragging = false;
+ break;
+ }
+
+ return false;
+ }
+
+ /**
+ * Overrides to provide fading when slide removal is enabled.
+ */
+ @Override
+ public void onDragFloatView(View floatView, Point position, Point touch) {
+
+ if (mRemoveEnabled) {
+ int x = touch.x;
+
+ if (mRemoveMode == SLIDE_RIGHT_REMOVE) {
+ int width = mDslv.getWidth();
+ int thirdWidth = width / 3;
+
+ float alpha;
+ if (x < thirdWidth) {
+ alpha = 1.0f;
+ } else if (x < width - thirdWidth) {
+ alpha = ((float) (width - thirdWidth - x)) / ((float) thirdWidth);
+ } else {
+ alpha = 0.0f;
+ }
+ mDslv.setFloatAlpha(mOrigFloatAlpha * alpha);
+ } else if (mRemoveMode == SLIDE_LEFT_REMOVE) {
+ int width = mDslv.getWidth();
+ int thirdWidth = width / 3;
+
+ float alpha;
+ if (x < thirdWidth) {
+ alpha = 0.0f;
+ } else if (x < width - thirdWidth) {
+ alpha = ((float) (x - thirdWidth)) / ((float) thirdWidth);
+ } else {
+ alpha = 1.0f;
+ }
+ mDslv.setFloatAlpha(mOrigFloatAlpha * alpha);
+ }
+ }
+ }
+
+ /**
+ * Get the position to start dragging based on the ACTION_DOWN
+ * MotionEvent. This function simply calls
+ * {@link #dragHandleHitPosition(MotionEvent)}. Override
+ * to change drag handle behavior;
+ * this function is called internally when an ACTION_DOWN
+ * event is detected.
+ *
+ * @param ev The ACTION_DOWN MotionEvent.
+ *
+ * @return The list position to drag if a drag-init gesture is
+ * detected; MISS if unsuccessful.
+ */
+ public int startDragPosition(MotionEvent ev) {
+ return dragHandleHitPosition(ev);
+ }
+
+ /**
+ * Checks for the touch of an item's drag handle (specified by
+ * {@link #setDragHandleId(int)}), and returns that item's position
+ * if a drag handle touch was detected.
+ *
+ * @param ev The ACTION_DOWN MotionEvent.
+
+ * @return The list position of the item whose drag handle was
+ * touched; MISS if unsuccessful.
+ */
+ public int dragHandleHitPosition(MotionEvent ev) {
+ return viewIdHitPosition(ev, mDragHandleId);
+ }
+
+ public int viewIdHitPosition(MotionEvent ev, int id) {
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+
+ int touchPos = mDslv.pointToPosition(x, y); //includes headers/footers
+
+ final int numHeaders = mDslv.getHeaderViewsCount();
+ final int numFooters = mDslv.getFooterViewsCount();
+ final int count = mDslv.getCount();
+
+ //Log.d("mobeta", "touch down on position " + itemnum);
+ // We're only interested if the touch was on an
+ // item that's not a header or footer.
+ if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders && touchPos < (count - numFooters)) {
+ final View item = mDslv.getChildAt(touchPos - mDslv.getFirstVisiblePosition());
+ final int rawX = (int) ev.getRawX();
+ final int rawY = (int) ev.getRawY();
+
+ //View dragBox = (View) item.getTag();
+ View dragBox = (View) item.findViewById(id);
+ if (dragBox != null) {
+ dragBox.getLocationOnScreen(mTempLoc);
+
+ if (rawX > mTempLoc[0] && rawY > mTempLoc[1] &&
+ rawX < mTempLoc[0] + dragBox.getWidth() &&
+ rawY < mTempLoc[1] + dragBox.getHeight()) {
+
+ mItemX = item.getLeft();
+ mItemY = item.getTop();
+
+ return touchPos;
+ }
+ }
+ }
+
+ return MISS;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent ev) {
+ if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
+ mClickRemoveHitPos = viewIdHitPosition(ev, mClickRemoveId);
+ }
+
+ mHitPos = startDragPosition(ev);
+ if (mHitPos != MISS && mDragInitMode == ON_DOWN) {
+ startDrag(mHitPos, (int) ev.getX() - mItemX, (int) ev.getY() - mItemY);
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ //Log.d("mobeta", "lift listener scrolled dX="+distanceX+" dY="+distanceY);
+
+ if (mHitPos != MISS && mDragInitMode == ON_DRAG && !mDragging) {
+ final int x1 = (int) e1.getX();
+ final int y1 = (int) e1.getY();
+ final int x2 = (int) e2.getX();
+ final int y2 = (int) e2.getY();
+
+ boolean start = false;
+ if (mRemoveEnabled && mSortEnabled) {
+ start = true;
+ } else if (mRemoveEnabled) {
+ start = Math.abs(x2 - x1) > mTouchSlop;
+ } else if (mSortEnabled) {
+ start = Math.abs(y2 - y1) > mTouchSlop;
+ }
+
+ if (start) {
+ startDrag(mHitPos, x2 - mItemX, y2 - mItemY);
+ }
+ }
+ // return whatever
+ return false;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ //Log.d("mobeta", "lift listener long pressed");
+ if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) {
+ mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY);
+ }
+ }
+
+ // complete the OnGestureListener interface
+ @Override
+ public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ return false;
+ }
+
+ // complete the OnGestureListener interface
+ @Override
+ public boolean onSingleTapUp(MotionEvent ev) {
+ if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
+ if (mClickRemoveHitPos != MISS) {
+ mDslv.removeItem(mClickRemoveHitPos - mDslv.getHeaderViewsCount());
+ }
+ }
+ return true;
+ }
+
+ // complete the OnGestureListener interface
+ @Override
+ public void onShowPress(MotionEvent ev) {
+ // do nothing
+ }
+
+ private GestureDetector.OnGestureListener mFlingRemoveListener =
+ new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ //Log.d("mobeta", "on fling remove called");
+ if (mRemoveEnabled) {
+ switch (mRemoveMode) {
+ case FLING_RIGHT_REMOVE:
+ if (velocityX > mFlingSpeed) {
+ mDslv.stopDrag(true);
+ }
+ break;
+ case FLING_LEFT_REMOVE:
+ if (velocityX < -mFlingSpeed) {
+ mDslv.stopDrag(true);
+ }
+ break;
+ }
+ }
+ return false;
+ }
+ };
+
+}
+
diff --git a/src/org/omnirom/omnigears/ui/dslv/DragSortCursorAdapter.java b/src/org/omnirom/omnigears/ui/dslv/DragSortCursorAdapter.java
new file mode 100644
index 0000000..31b3f11
--- /dev/null
+++ b/src/org/omnirom/omnigears/ui/dslv/DragSortCursorAdapter.java
@@ -0,0 +1,261 @@
+/**
+ * A subclass of the Android ListView component that enables drag
+ * and drop re-ordering of list items.
+ *
+ * Copyright 2012 Carl Bauer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.omnigears.ui.dslv;
+
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+
+
+/**
+ * A subclass of {@link android.widget.CursorAdapter} that provides
+ * reordering of the elements in the Cursor based on completed
+ * drag-sort operations. The reordering is a simple mapping of
+ * list positions into Cursor positions (the Cursor is unchanged).
+ * To persist changes made by drag-sorts, one can retrieve the
+ * mapping with the {@link #getCursorPositions()} method, which
+ * returns the reordered list of Cursor positions.
+ *
+ * An instance of this class is passed
+ * to {@link DragSortListView#setAdapter(ListAdapter)} and, since
+ * this class implements the {@link DragSortListView.DragSortListener}
+ * interface, it is automatically set as the DragSortListener for
+ * the DragSortListView instance.
+ */
+public abstract class DragSortCursorAdapter extends CursorAdapter implements DragSortListView.DragSortListener {
+
+ public static final int REMOVED = -1;
+
+ /**
+ * Key is ListView position, value is Cursor position
+ */
+ private SparseIntArray mListMapping = new SparseIntArray();
+
+ private ArrayList<Integer> mRemovedCursorPositions = new ArrayList<Integer>();
+
+ @SuppressWarnings("deprecation")
+ public DragSortCursorAdapter(Context context, Cursor c) {
+ super(context, c);
+ }
+
+ public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) {
+ super(context, c, autoRequery);
+ }
+
+ public DragSortCursorAdapter(Context context, Cursor c, int flags) {
+ super(context, c, flags);
+ }
+
+ /**
+ * Swaps Cursor and clears list-Cursor mapping.
+ *
+ * @see android.widget.CursorAdapter#swapCursor(android.database.Cursor)
+ */
+ @Override
+ public Cursor swapCursor(Cursor newCursor) {
+ Cursor old = super.swapCursor(newCursor);
+ resetMappings();
+ return old;
+ }
+
+ /**
+ * Changes Cursor and clears list-Cursor mapping.
+ *
+ * @see android.widget.CursorAdapter#changeCursor(android.database.Cursor)
+ */
+ @Override
+ public void changeCursor(Cursor cursor) {
+ super.changeCursor(cursor);
+ resetMappings();
+ }
+
+ /**
+ * Resets list-cursor mapping.
+ */
+ public void reset() {
+ resetMappings();
+ notifyDataSetChanged();
+ }
+
+ private void resetMappings() {
+ mListMapping.clear();
+ mRemovedCursorPositions.clear();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return super.getItem(mListMapping.get(position, position));
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return super.getItemId(mListMapping.get(position, position));
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ return super.getDropDownView(mListMapping.get(position, position), convertView, parent);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return super.getView(mListMapping.get(position, position), convertView, parent);
+ }
+
+ /**
+ * On drop, this updates the mapping between Cursor positions
+ * and ListView positions. The Cursor is unchanged. Retrieve
+ * the current mapping with {@link getCursorPositions()}.
+ *
+ * @see DragSortListView.DropListener#drop(int, int)
+ */
+ @Override
+ public void drop(int from, int to) {
+ if (from != to) {
+ int cursorFrom = mListMapping.get(from, from);
+
+ if (from > to) {
+ for (int i = from; i > to; --i) {
+ mListMapping.put(i, mListMapping.get(i - 1, i - 1));
+ }
+ } else {
+ for (int i = from; i < to; ++i) {
+ mListMapping.put(i, mListMapping.get(i + 1, i + 1));
+ }
+ }
+ mListMapping.put(to, cursorFrom);
+
+ cleanMapping();
+ notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * On remove, this updates the mapping between Cursor positions
+ * and ListView positions. The Cursor is unchanged. Retrieve
+ * the current mapping with {@link getCursorPositions()}.
+ *
+ * @see DragSortListView.RemoveListener#remove(int)
+ */
+ @Override
+ public void remove(int which) {
+ int cursorPos = mListMapping.get(which, which);
+ if (!mRemovedCursorPositions.contains(cursorPos)) {
+ mRemovedCursorPositions.add(cursorPos);
+ }
+
+ int newCount = getCount();
+ for (int i = which; i < newCount; ++i) {
+ mListMapping.put(i, mListMapping.get(i + 1, i + 1));
+ }
+
+ mListMapping.delete(newCount);
+
+ cleanMapping();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Does nothing. Just completes DragSortListener interface.
+ */
+ @Override
+ public void drag(int from, int to) {
+ // do nothing
+ }
+
+ /**
+ * Remove unnecessary mappings from sparse array.
+ */
+ private void cleanMapping() {
+ ArrayList<Integer> toRemove = new ArrayList<Integer>();
+
+ int size = mListMapping.size();
+ for (int i = 0; i < size; ++i) {
+ if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) {
+ toRemove.add(mListMapping.keyAt(i));
+ }
+ }
+
+ size = toRemove.size();
+ for (int i = 0; i < size; ++i) {
+ mListMapping.delete(toRemove.get(i));
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return super.getCount() - mRemovedCursorPositions.size();
+ }
+
+ /**
+ * Get the Cursor position mapped to by the provided list position
+ * (given all previously handled drag-sort
+ * operations).
+ *
+ * @param position List position
+ *
+ * @return The mapped-to Cursor position
+ */
+ public int getCursorPosition(int position) {
+ return mListMapping.get(position, position);
+ }
+
+ /**
+ * Get the current order of Cursor positions presented by the
+ * list.
+ */
+ public ArrayList<Integer> getCursorPositions() {
+ ArrayList<Integer> result = new ArrayList<Integer>();
+
+ for (int i = 0; i < getCount(); ++i) {
+ result.add(mListMapping.get(i, i));
+ }
+
+ return result;
+ }
+
+ /**
+ * Get the list position mapped to by the provided Cursor position.
+ * If the provided Cursor position has been removed by a drag-sort,
+ * this returns {@link #REMOVED}.
+ *
+ * @param cursorPosition A Cursor position
+ * @return The mapped-to list position or REMOVED
+ */
+ public int getListPosition(int cursorPosition) {
+ if (mRemovedCursorPositions.contains(cursorPosition)) {
+ return REMOVED;
+ }
+
+ int index = mListMapping.indexOfValue(cursorPosition);
+ if (index < 0) {
+ return cursorPosition;
+ } else {
+ return mListMapping.keyAt(index);
+ }
+ }
+
+
+}
diff --git a/src/org/omnirom/omnigears/ui/dslv/DragSortItemView.java b/src/org/omnirom/omnigears/ui/dslv/DragSortItemView.java
new file mode 100644
index 0000000..d837eb5
--- /dev/null
+++ b/src/org/omnirom/omnigears/ui/dslv/DragSortItemView.java
@@ -0,0 +1,114 @@
+/**
+ * A subclass of the Android ListView component that enables drag
+ * and drop re-ordering of list items.
+ *
+ * Copyright 2012 Carl Bauer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.omnigears.ui.dslv;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+
+/**
+ * Lightweight ViewGroup that wraps list items obtained from user's
+ * ListAdapter. ItemView expects a single child that has a definite
+ * height (i.e. the child's layout height is not MATCH_PARENT).
+ * The width of
+ * ItemView will always match the width of its child (that is,
+ * the width MeasureSpec given to ItemView is passed directly
+ * to the child, and the ItemView measured width is set to the
+ * child's measured width). The height of ItemView can be anything;
+ *
+ * The purpose of this class is to optimize slide
+ * shuffle animations.
+ */
+public class DragSortItemView extends ViewGroup {
+
+ private int mGravity = Gravity.TOP;
+
+ public DragSortItemView(Context context) {
+ super(context);
+
+ // always init with standard ListView layout params
+ setLayoutParams(new AbsListView.LayoutParams(
+ LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT));
+
+ setClipChildren(true);
+ }
+
+ public void setGravity(int gravity) {
+ mGravity = gravity;
+ }
+
+ public int getGravity() {
+ return mGravity;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ final View child = getChildAt(0);
+
+ if (child == null) {
+ return;
+ }
+
+ if (mGravity == Gravity.TOP) {
+ child.layout(0, 0, getMeasuredWidth(), child.getMeasuredHeight());
+ } else {
+ child.layout(0, getMeasuredHeight() - child.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight());
+ }
+ }
+
+ /**
+ *
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ final View child = getChildAt(0);
+ if (child == null) {
+ setMeasuredDimension(0, width);
+ return;
+ }
+
+ if (child.isLayoutRequested()) {
+ // Always let child be as tall as it wants.
+ measureChild(child, widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ }
+
+ if (heightMode == MeasureSpec.UNSPECIFIED) {
+ LayoutParams lp = getLayoutParams();
+
+ if (lp.height > 0) {
+ height = lp.height;
+ } else {
+ height = child.getMeasuredHeight();
+ }
+ }
+
+ setMeasuredDimension(width, height);
+ }
+
+}
diff --git a/src/org/omnirom/omnigears/ui/dslv/DragSortListView.java b/src/org/omnirom/omnigears/ui/dslv/DragSortListView.java
new file mode 100644
index 0000000..9a83410
--- /dev/null
+++ b/src/org/omnirom/omnigears/ui/dslv/DragSortListView.java
@@ -0,0 +1,2638 @@
+/*
+ * DragSortListView.
+ *
+ * A subclass of the Android ListView component that enables drag
+ * and drop re-ordering of list items.
+ *
+ * Copyright 2012 Carl Bauer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.omnigears.ui.dslv;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.omnirom.omnigears.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.BaseAdapter;
+import android.widget.HeaderViewListAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+
+/**
+ * ListView subclass that mediates drag and drop resorting of items.
+ *
+ *
+ * @author heycosmo
+ *
+ */
+public class DragSortListView extends ListView {
+ /**
+ * The View that floats above the ListView and represents
+ * the dragged item.
+ */
+ private View mFloatView;
+
+ /**
+ * The float View location. First based on touch location
+ * and given deltaX and deltaY. Then restricted by callback
+ * to FloatViewManager.onDragFloatView(). Finally restricted
+ * by bounds of DSLV.
+ */
+ private Point mFloatLoc = new Point();
+
+ private Point mTouchLoc = new Point();
+
+ /**
+ * The middle (in the y-direction) of the floating View.
+ */
+ private int mFloatViewMid;
+
+ /**
+ * Flag to make sure float View isn't measured twice
+ */
+ private boolean mFloatViewOnMeasured = false;
+
+ /**
+ * Watch the Adapter for data changes. Cancel a drag if
+ * coincident with a change.
+ */
+ private DataSetObserver mObserver;
+
+ /**
+ * Transparency for the floating View (XML attribute).
+ */
+ private float mFloatAlpha = 1.0f;
+ private float mCurrFloatAlpha = 1.0f;
+
+ /**
+ * While drag-sorting, the current position of the floating
+ * View. If dropped, the dragged item will land in this position.
+ */
+ private int mFloatPos;
+
+ /**
+ * The first expanded ListView position that helps represent
+ * the drop slot tracking the floating View.
+ */
+ private int mFirstExpPos;
+
+ /**
+ * The second expanded ListView position that helps represent
+ * the drop slot tracking the floating View. This can equal
+ * mFirstExpPos if there is no slide shuffle occurring; otherwise
+ * it is equal to mFirstExpPos + 1.
+ */
+ private int mSecondExpPos;
+
+ /**
+ * Flag set if slide shuffling is enabled.
+ */
+ private boolean mAnimate = false;
+
+ /**
+ * The user dragged from this position.
+ */
+ private int mSrcPos;
+
+ /**
+ * Offset (in x) within the dragged item at which the user
+ * picked it up (or first touched down with the digitalis).
+ */
+ private int mDragDeltaX;
+
+ /**
+ * Offset (in y) within the dragged item at which the user
+ * picked it up (or first touched down with the digitalis).
+ */
+ private int mDragDeltaY;
+
+ /**
+ * A listener that receives callbacks whenever the floating View
+ * hovers over a new position.
+ */
+ private DragListener mDragListener;
+
+ /**
+ * A listener that receives a callback when the floating View
+ * is dropped.
+ */
+ private DropListener mDropListener;
+
+ /**
+ * A listener that receives a callback when the floating View
+ * (or more precisely the originally dragged item) is removed
+ * by one of the provided gestures.
+ */
+ private RemoveListener mRemoveListener;
+
+ /**
+ * Enable/Disable item dragging
+ */
+ private boolean mDragEnabled = true;
+
+ /**
+ * Drag state enum.
+ */
+ private final static int IDLE = 0;
+ private final static int REMOVING = 1;
+ private final static int DROPPING = 2;
+ private final static int STOPPED = 3;
+ private final static int DRAGGING = 4;
+
+ private int mDragState = IDLE;
+
+ /**
+ * Height in pixels to which the originally dragged item
+ * is collapsed during a drag-sort. Currently, this value
+ * must be greater than zero.
+ */
+ private int mItemHeightCollapsed = 1;
+
+ /**
+ * Height of the floating View. Stored for the purpose of
+ * providing the tracking drop slot.
+ */
+ private int mFloatViewHeight;
+
+ /**
+ * Convenience member. See above.
+ */
+ private int mFloatViewHeightHalf;
+
+ /**
+ * Save the given width spec for use in measuring children
+ */
+ private int mWidthMeasureSpec = 0;
+
+ /**
+ * Sample Views ultimately used for calculating the height
+ * of ListView items that are off-screen.
+ */
+ private View[] mSampleViewTypes = new View[1];
+
+ /**
+ * Drag-scroll encapsulator!
+ */
+ private DragScroller mDragScroller;
+
+ /**
+ * Determines the start of the upward drag-scroll region
+ * at the top of the ListView. Specified by a fraction
+ * of the ListView height, thus screen resolution agnostic.
+ */
+ private float mDragUpScrollStartFrac = 1.0f / 3.0f;
+
+ /**
+ * Determines the start of the downward drag-scroll region
+ * at the bottom of the ListView. Specified by a fraction
+ * of the ListView height, thus screen resolution agnostic.
+ */
+ private float mDragDownScrollStartFrac = 1.0f / 3.0f;
+
+ /**
+ * The following are calculated from the above fracs.
+ */
+ private int mUpScrollStartY;
+ private int mDownScrollStartY;
+ private float mDownScrollStartYF;
+ private float mUpScrollStartYF;
+
+ /**
+ * Calculated from above above and current ListView height.
+ */
+ private float mDragUpScrollHeight;
+
+ /**
+ * Calculated from above above and current ListView height.
+ */
+ private float mDragDownScrollHeight;
+
+
+ /**
+ * Maximum drag-scroll speed in pixels per ms. Only used with
+ * default linear drag-scroll profile.
+ */
+ private float mMaxScrollSpeed = 0.5f;
+
+ /**
+ * Defines the scroll speed during a drag-scroll. User can
+ * provide their own; this default is a simple linear profile
+ * where scroll speed increases linearly as the floating View
+ * nears the top/bottom of the ListView.
+ */
+ private DragScrollProfile mScrollProfile = new DragScrollProfile() {
+ @Override
+ public float getSpeed(float w, long t) {
+ return mMaxScrollSpeed * w;
+ }
+ };
+
+ /**
+ * Current touch x.
+ */
+ private int mX;
+
+ /**
+ * Current touch y.
+ */
+ private int mY;
+
+ /**
+ * Last touch y.
+ */
+ private int mLastY;
+
+ /**
+ * Drag flag bit. Floating View can move in the positive
+ * x direction.
+ */
+ public final static int DRAG_POS_X = 0x1;
+
+ /**
+ * Drag flag bit. Floating View can move in the negative
+ * x direction.
+ */
+ public final static int DRAG_NEG_X = 0x2;
+
+ /**
+ * Drag flag bit. Floating View can move in the positive
+ * y direction. This is subtle. What this actually means is
+ * that, if enabled, the floating View can be dragged below its starting
+ * position. Remove in favor of upper-bounding item position?
+ */
+ public final static int DRAG_POS_Y = 0x4;
+
+ /**
+ * Drag flag bit. Floating View can move in the negative
+ * y direction. This is subtle. What this actually means is
+ * that the floating View can be dragged above its starting
+ * position. Remove in favor of lower-bounding item position?
+ */
+ public final static int DRAG_NEG_Y = 0x8;
+
+ /**
+ * Flags that determine limits on the motion of the
+ * floating View. See flags above.
+ */
+ private int mDragFlags = 0;
+
+ /**
+ * Last call to an on*TouchEvent was a call to
+ * onInterceptTouchEvent.
+ */
+ private boolean mLastCallWasIntercept = false;
+
+ /**
+ * A touch event is in progress.
+ */
+ private boolean mInTouchEvent = false;
+
+ /**
+ * Let the user customize the floating View.
+ */
+ private FloatViewManager mFloatViewManager = null;
+
+ /**
+ * Given to ListView to cancel its action when a drag-sort
+ * begins.
+ */
+ private MotionEvent mCancelEvent;
+
+ /**
+ * Enum telling where to cancel the ListView action when a
+ * drag-sort begins
+ */
+ private static final int NO_CANCEL = 0;
+ private static final int ON_TOUCH_EVENT = 1;
+ private static final int ON_INTERCEPT_TOUCH_EVENT = 2;
+
+ /**
+ * Where to cancel the ListView action when a
+ * drag-sort begins
+ */
+ private int mCancelMethod = NO_CANCEL;
+
+ /**
+ * Determines when a slide shuffle animation starts. That is,
+ * defines how close to the edge of the drop slot the floating
+ * View must be to initiate the slide.
+ */
+ private float mSlideRegionFrac = 0.25f;
+
+ /**
+ * Number between 0 and 1 indicating the relative location of
+ * a sliding item (only used if drag-sort animations
+ * are turned on). Nearly 1 means the item is
+ * at the top of the slide region (nearly full blank item
+ * is directly below).
+ */
+ private float mSlideFrac = 0.0f;
+
+ /**
+ * Wraps the user-provided ListAdapter. This is used to wrap each
+ * item View given by the user inside another View (currenly
+ * a RelativeLayout) which
+ * expands and collapses to simulate the item shuffling.
+ */
+ private AdapterWrapper mAdapterWrapper;
+
+ /**
+ * Turn on custom debugger.
+ */
+ private boolean mTrackDragSort = false;
+
+ /**
+ * Debugging class.
+ */
+ private DragSortTracker mDragSortTracker;
+
+ /**
+ * Needed for adjusting item heights from within layoutChildren
+ */
+ private boolean mBlockLayoutRequests = false;
+
+ /**
+ * Set to true when a down event happens during drag sort;
+ * for example, when drag finish animations are
+ * playing.
+ */
+ private boolean mIgnoreTouchEvent = false;
+
+ /**
+ * Caches DragSortItemView child heights. Sometimes DSLV has to
+ * know the height of an offscreen item. Since ListView virtualizes
+ * these, DSLV must get the item from the ListAdapter to obtain
+ * its height. That process can be expensive, but often the same
+ * offscreen item will be requested many times in a row. Once an
+ * offscreen item height is calculated, we cache it in this guy.
+ * Actually, we cache the height of the child of the
+ * DragSortItemView since the item height changes often during a
+ * drag-sort.
+ */
+ private static final int sCacheSize = 3;
+ private HeightCache mChildHeightCache = new HeightCache(sCacheSize);
+
+ private RemoveAnimator mRemoveAnimator;
+
+ private LiftAnimator mLiftAnimator;
+
+ private DropAnimator mDropAnimator;
+
+ public DragSortListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ int defaultDuration = 150;
+ int removeAnimDuration = defaultDuration; //ms
+ int dropAnimDuration = defaultDuration; //ms
+
+ if (attrs != null) {
+ TypedArray a = getContext().obtainStyledAttributes(attrs,
+ R.styleable.DragSortListView, 0, 0);
+
+ mItemHeightCollapsed = Math.max(1, a.getDimensionPixelSize(
+ R.styleable.DragSortListView_collapsed_height, 1));
+
+ mTrackDragSort = a.getBoolean(
+ R.styleable.DragSortListView_track_drag_sort, false);
+
+ if (mTrackDragSort) {
+ mDragSortTracker = new DragSortTracker();
+ }
+
+ // alpha between 0 and 255, 0=transparent, 255=opaque
+ mFloatAlpha = a.getFloat(R.styleable.DragSortListView_float_alpha, mFloatAlpha);
+ mCurrFloatAlpha = mFloatAlpha;
+
+ mDragEnabled = a.getBoolean(R.styleable.DragSortListView_drag_enabled, mDragEnabled);
+
+ mSlideRegionFrac = Math.max(0.0f,
+ Math.min(1.0f, 1.0f - a.getFloat(
+ R.styleable.DragSortListView_slide_shuffle_speed,
+ 0.75f)));
+
+ mAnimate = mSlideRegionFrac > 0.0f;
+
+ float frac = a.getFloat(
+ R.styleable.DragSortListView_drag_scroll_start,
+ mDragUpScrollStartFrac);
+
+ setDragScrollStart(frac);
+
+ mMaxScrollSpeed = a.getFloat(
+ R.styleable.DragSortListView_max_drag_scroll_speed,
+ mMaxScrollSpeed);
+
+ removeAnimDuration = a.getInt(
+ R.styleable.DragSortListView_remove_animation_duration,
+ removeAnimDuration);
+
+ dropAnimDuration = a.getInt(
+ R.styleable.DragSortListView_drop_animation_duration,
+ dropAnimDuration);
+
+ boolean useDefault = a.getBoolean(
+ R.styleable.DragSortListView_use_default_controller,
+ true);
+
+ if (useDefault) {
+ boolean removeEnabled = a.getBoolean(
+ R.styleable.DragSortListView_remove_enabled,
+ false);
+ int removeMode = a.getInt(
+ R.styleable.DragSortListView_remove_mode,
+ DragSortController.FLING_RIGHT_REMOVE);
+ boolean sortEnabled = a.getBoolean(
+ R.styleable.DragSortListView_sort_enabled,
+ true);
+ int dragInitMode = a.getInt(
+ R.styleable.DragSortListView_drag_start_mode,
+ DragSortController.ON_DOWN);
+ int dragHandleId = a.getResourceId(
+ R.styleable.DragSortListView_drag_handle_id,
+ 0);
+ int clickRemoveId = a.getResourceId(
+ R.styleable.DragSortListView_click_remove_id,
+ 0);
+ int bgColor = a.getColor(
+ R.styleable.DragSortListView_float_background_color,
+ Color.BLACK);
+
+ DragSortController controller = new DragSortController(
+ this, dragHandleId, dragInitMode, removeMode,
+ clickRemoveId);
+ controller.setRemoveEnabled(removeEnabled);
+ controller.setSortEnabled(sortEnabled);
+ controller.setBackgroundColor(bgColor);
+
+ mFloatViewManager = controller;
+ setOnTouchListener(controller);
+ }
+
+ a.recycle();
+ }
+
+ mDragScroller = new DragScroller();
+
+ float smoothness = 0.5f;
+ if (removeAnimDuration > 0) {
+ mRemoveAnimator = new RemoveAnimator(smoothness, removeAnimDuration);
+ }
+ //mLiftAnimator = new LiftAnimator(smoothness, 100);
+ if (dropAnimDuration > 0) {
+ mDropAnimator = new DropAnimator(smoothness, dropAnimDuration);
+ }
+
+ if(mCancelEvent!=null){
+ mCancelEvent.recycle();
+ }
+ mCancelEvent = MotionEvent.obtain(0,0,MotionEvent.ACTION_CANCEL,0f,0f,0f,0f,0,0f,0f,0,0);
+
+ // construct the dataset observer
+ mObserver = new DataSetObserver() {
+ private void cancel() {
+ if (mDragState == DRAGGING) {
+ cancelDrag();
+ }
+ }
+
+ @Override
+ public void onChanged() {
+ cancel();
+ }
+
+ @Override
+ public void onInvalidated() {
+ cancel();
+ }
+ };
+ }
+
+ /**
+ * Usually called from a FloatViewManager. The float alpha
+ * will be reset to the xml-defined value every time a drag
+ * is stopped.
+ */
+ public void setFloatAlpha(float alpha) {
+ mCurrFloatAlpha = alpha;
+ }
+
+ public float getFloatAlpha() {
+ return mCurrFloatAlpha;
+ }
+
+ /**
+ * Set maximum drag scroll speed in positions/second. Only applies
+ * if using default ScrollSpeedProfile.
+ *
+ * @param max Maximum scroll speed.
+ */
+ public void setMaxScrollSpeed(float max) {
+ mMaxScrollSpeed = max;
+ }
+
+ /**
+ * For each DragSortListView Listener interface implemented by
+ * <code>adapter</code>, this method calls the appropriate
+ * set*Listener method with <code>adapter</code> as the argument.
+ *
+ * @param adapter The ListAdapter providing data to back
+ * DragSortListView.
+ *
+ * @see android.widget.ListView#setAdapter(android.widget.ListAdapter)
+ */
+ @Override
+ public void setAdapter(ListAdapter adapter) {
+ mAdapterWrapper = new AdapterWrapper(adapter);
+
+ if (adapter != null) {
+ adapter.registerDataSetObserver(mObserver);
+
+ if (adapter instanceof DropListener) {
+ setDropListener((DropListener) adapter);
+ }
+ if (adapter instanceof DragListener) {
+ setDragListener((DragListener) adapter);
+ }
+ if (adapter instanceof RemoveListener) {
+ setRemoveListener((RemoveListener) adapter);
+ }
+ }
+
+ super.setAdapter(mAdapterWrapper);
+ }
+
+ /**
+ * As opposed to {@link ListView#getAdapter()}, which returns
+ * a heavily wrapped ListAdapter (DragSortListView wraps the
+ * input ListAdapter {\emph and} ListView wraps the wrapped one).
+ *
+ * @return The ListAdapter set as the argument of {@link setAdapter()}
+ */
+ public ListAdapter getInputAdapter() {
+ if (mAdapterWrapper == null) {
+ return null;
+ } else {
+ return mAdapterWrapper.getAdapter();
+ }
+ }
+
+ private class AdapterWrapper extends HeaderViewListAdapter {
+ private ListAdapter mAdapter;
+
+ public AdapterWrapper(ListAdapter adapter) {
+ super(null, null, adapter);
+ mAdapter = adapter;
+ }
+
+ public ListAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+
+ DragSortItemView v;
+ View child;
+ //Log.d("mobeta", "getView: position="+position+" convertView="+convertView);
+ if (convertView != null) {
+ v = (DragSortItemView) convertView;
+ View oldChild = v.getChildAt(0);
+
+ child = mAdapter.getView(position, oldChild, v);
+ if (child != oldChild) {
+ // shouldn't get here if user is reusing convertViews properly
+ v.removeViewAt(0);
+ v.addView(child);
+ }
+ } else {
+ v = new DragSortItemView(getContext());
+ v.setLayoutParams(new AbsListView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ child = mAdapter.getView(position, null, v);
+ v.addView(child);
+ }
+
+ // Set the correct item height given drag state; passed
+ // View needs to be measured if measurement is required.
+ adjustItem(position + getHeaderViewsCount(), v, true);
+
+ return v;
+ }
+ }
+
+ private void drawDivider(int expPosition, Canvas canvas) {
+
+ final Drawable divider = getDivider();
+ final int dividerHeight = getDividerHeight();
+ //Log.d("mobeta", "div="+divider+" divH="+dividerHeight);
+
+ if (divider != null && dividerHeight != 0) {
+ final ViewGroup expItem = (ViewGroup) getChildAt(expPosition - getFirstVisiblePosition());
+ if (expItem != null) {
+ final int l = getPaddingLeft();
+ final int r = getWidth() - getPaddingRight();
+ final int t;
+ final int b;
+
+ final int childHeight = expItem.getChildAt(0).getHeight();
+
+ if (expPosition > mSrcPos) {
+ t = expItem.getTop() + childHeight;
+ b = t + dividerHeight;
+ } else {
+ b = expItem.getBottom() - childHeight;
+ t = b - dividerHeight;
+ }
+ //Log.d("mobeta", "l="+l+" t="+t+" r="+r+" b="+b);
+
+ // Have to clip to support ColorDrawable on <= Gingerbread
+ canvas.save();
+ canvas.clipRect(l, t, r, b);
+ divider.setBounds(l, t, r, b);
+ divider.draw(canvas);
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ if (mDragState != IDLE) {
+ // draw the divider over the expanded item
+ if (mFirstExpPos != mSrcPos) {
+ drawDivider(mFirstExpPos, canvas);
+ }
+ if (mSecondExpPos != mFirstExpPos && mSecondExpPos != mSrcPos) {
+ drawDivider(mSecondExpPos, canvas);
+ }
+ }
+
+ if (mFloatView != null) {
+ // draw the float view over everything
+ final int w = mFloatView.getWidth();
+ final int h = mFloatView.getHeight();
+ final int alpha = (int) (255f * mCurrFloatAlpha);
+
+ canvas.save();
+ //Log.d("mobeta", "clip rect bounds: " + canvas.getClipBounds());
+ canvas.translate(mFloatLoc.x, mFloatLoc.y);
+ canvas.clipRect(0, 0, w, h);
+
+ //Log.d("mobeta", "clip rect bounds: " + canvas.getClipBounds());
+ canvas.saveLayerAlpha(0, 0, w, h, alpha, Canvas.ALL_SAVE_FLAG);
+ mFloatView.draw(canvas);
+ canvas.restore();
+ canvas.restore();
+ }
+ }
+
+ private int getItemHeight(int position) {
+ View v = getChildAt(position - getFirstVisiblePosition());
+
+ if (v != null) {
+ // item is onscreen, just get the height of the View
+ return v.getHeight();
+ } else {
+ // item is offscreen. get child height and calculate
+ // item height based on current shuffle state
+ return calcItemHeight(position, getChildHeight(position));
+ }
+ }
+
+ private class HeightCache {
+
+ private SparseIntArray mMap;
+ private ArrayList<Integer> mOrder;
+ private int mMaxSize;
+
+ public HeightCache(int size) {
+ mMap = new SparseIntArray(size);
+ mOrder = new ArrayList<Integer>(size);
+ mMaxSize = size;
+ }
+
+ /**
+ * Add item height at position if doesn't already exist.
+ */
+ public void add(int position, int height) {
+ int currHeight = mMap.get(position, -1);
+ if (currHeight != height) {
+ if (currHeight == -1) {
+ if (mMap.size() == mMaxSize) {
+ // remove oldest entry
+ mMap.delete(mOrder.remove(0));
+ }
+ } else {
+ // move position to newest slot
+ mOrder.remove((Integer) position);
+ }
+ mMap.put(position, height);
+ mOrder.add(position);
+ }
+ }
+
+ public int get(int position) {
+ return mMap.get(position, -1);
+ }
+
+ public void clear() {
+ mMap.clear();
+ mOrder.clear();
+ }
+
+ }
+
+ /**
+ * Get the shuffle edge for item at position when top of
+ * item is at y-coord top. Assumes that current item heights
+ * are consistent with current float view location and
+ * thus expanded positions and slide fraction. i.e. Should not be
+ * called between update of expanded positions/slide fraction
+ * and layoutChildren.
+ *
+ * @param position
+ * @param top
+ * @param height Height of item at position. If -1, this function
+ * calculates this height.
+ *
+ * @return Shuffle line between position-1 and position (for
+ * the given view of the list; that is, for when top of item at
+ * position has y-coord of given `top`). If
+ * floating View (treated as horizontal line) is dropped
+ * immediately above this line, it lands in position-1. If
+ * dropped immediately below this line, it lands in position.
+ */
+ private int getShuffleEdge(int position, int top) {
+
+ final int numHeaders = getHeaderViewsCount();
+ final int numFooters = getFooterViewsCount();
+
+ // shuffle edges are defined between items that can be
+ // dragged; there are N-1 of them if there are N draggable
+ // items.
+
+ if (position <= numHeaders || (position >= getCount() - numFooters)) {
+ return top;
+ }
+
+ int divHeight = getDividerHeight();
+
+ int edge;
+
+ int maxBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
+ int childHeight = getChildHeight(position);
+ int itemHeight = getItemHeight(position);
+
+ // first calculate top of item given that floating View is
+ // centered over src position
+ int otop = top;
+ if (mSecondExpPos <= mSrcPos) {
+ // items are expanded on and/or above the source position
+
+ if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
+ if (position == mSrcPos) {
+ otop = top + itemHeight - mFloatViewHeight;
+ } else {
+ int blankHeight = itemHeight - childHeight;
+ otop = top + blankHeight - maxBlankHeight;
+ }
+ } else if (position > mSecondExpPos && position <= mSrcPos) {
+ otop = top - maxBlankHeight;
+ }
+
+ } else {
+ // items are expanded on and/or below the source position
+
+ if (position > mSrcPos && position <= mFirstExpPos) {
+ otop = top + maxBlankHeight;
+ } else if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
+ int blankHeight = itemHeight - childHeight;
+ otop = top + blankHeight;
+ }
+ }
+
+ // otop is set
+ if (position <= mSrcPos) {
+ edge = otop + (mFloatViewHeight - divHeight - getChildHeight(position - 1)) / 2;
+ } else {
+ edge = otop + (childHeight - divHeight - mFloatViewHeight) / 2;
+ }
+
+ return edge;
+ }
+
+ private boolean updatePositions() {
+
+ final int first = getFirstVisiblePosition();
+ int startPos = mFirstExpPos;
+ View startView = getChildAt(startPos - first);
+
+ if (startView == null) {
+ startPos = first + getChildCount() / 2;
+ startView = getChildAt(startPos - first);
+ }
+ int startTop = startView.getTop();
+
+ int itemHeight = startView.getHeight();
+
+ int edge = getShuffleEdge(startPos, startTop);
+ int lastEdge = edge;
+
+ int divHeight = getDividerHeight();
+
+ //Log.d("mobeta", "float mid="+mFloatViewMid);
+
+ int itemPos = startPos;
+ int itemTop = startTop;
+ if (mFloatViewMid < edge) {
+ // scanning up for float position
+ //Log.d("mobeta", " edge="+edge);
+ while (itemPos >= 0) {
+ itemPos--;
+ itemHeight = getItemHeight(itemPos);
+
+ if (itemPos == 0) {
+ edge = itemTop - divHeight - itemHeight;
+ break;
+ }
+
+ itemTop -= itemHeight + divHeight;
+ edge = getShuffleEdge(itemPos, itemTop);
+ //Log.d("mobeta", " edge="+edge);
+
+ if (mFloatViewMid >= edge) {
+ break;
+ }
+
+ lastEdge = edge;
+ }
+ } else {
+ // scanning down for float position
+ //Log.d("mobeta", " edge="+edge);
+ final int count = getCount();
+ while (itemPos < count) {
+ if (itemPos == count - 1) {
+ edge = itemTop + divHeight + itemHeight;
+ break;
+ }
+
+ itemTop += divHeight + itemHeight;
+ itemHeight = getItemHeight(itemPos + 1);
+ edge = getShuffleEdge(itemPos + 1, itemTop);
+ //Log.d("mobeta", " edge="+edge);
+
+ // test for hit
+ if (mFloatViewMid < edge) {
+ break;
+ }
+
+ lastEdge = edge;
+ itemPos++;
+ }
+ }
+
+ final int numHeaders = getHeaderViewsCount();
+ final int numFooters = getFooterViewsCount();
+
+ boolean updated = false;
+
+ int oldFirstExpPos = mFirstExpPos;
+ int oldSecondExpPos = mSecondExpPos;
+ float oldSlideFrac = mSlideFrac;
+
+ if (mAnimate) {
+ int edgeToEdge = Math.abs(edge - lastEdge);
+
+ int edgeTop, edgeBottom;
+ if (mFloatViewMid < edge) {
+ edgeBottom = edge;
+ edgeTop = lastEdge;
+ } else {
+ edgeTop = edge;
+ edgeBottom = lastEdge;
+ }
+ //Log.d("mobeta", "edgeTop="+edgeTop+" edgeBot="+edgeBottom);
+
+ int slideRgnHeight = (int) (0.5f * mSlideRegionFrac * edgeToEdge);
+ float slideRgnHeightF = (float) slideRgnHeight;
+ int slideEdgeTop = edgeTop + slideRgnHeight;
+ int slideEdgeBottom = edgeBottom - slideRgnHeight;
+
+ // Three regions
+ if (mFloatViewMid < slideEdgeTop) {
+ mFirstExpPos = itemPos - 1;
+ mSecondExpPos = itemPos;
+ mSlideFrac = 0.5f * ((float) (slideEdgeTop - mFloatViewMid)) / slideRgnHeightF;
+ //Log.d("mobeta", "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
+ } else if (mFloatViewMid < slideEdgeBottom) {
+ mFirstExpPos = itemPos;
+ mSecondExpPos = itemPos;
+ } else {
+ mFirstExpPos = itemPos;
+ mSecondExpPos = itemPos + 1;
+ mSlideFrac = 0.5f * (1.0f + ((float) (edgeBottom - mFloatViewMid)) / slideRgnHeightF);
+ //Log.d("mobeta", "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
+ }
+
+ } else {
+ mFirstExpPos = itemPos;
+ mSecondExpPos = itemPos;
+ }
+
+ // correct for headers and footers
+ if (mFirstExpPos < numHeaders) {
+ itemPos = numHeaders;
+ mFirstExpPos = itemPos;
+ mSecondExpPos = itemPos;
+ } else if (mSecondExpPos >= getCount() - numFooters) {
+ itemPos = getCount() - numFooters - 1;
+ mFirstExpPos = itemPos;
+ mSecondExpPos = itemPos;
+ }
+
+ if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos || mSlideFrac != oldSlideFrac) {
+ updated = true;
+ }
+
+ if (itemPos != mFloatPos) {
+ if (mDragListener != null) {
+ mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders);
+ }
+
+ mFloatPos = itemPos;
+ updated = true;
+ }
+
+ return updated;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mTrackDragSort) {
+ mDragSortTracker.appendState();
+ }
+ }
+
+ private class SmoothAnimator implements Runnable {
+ private long mStartTime;
+
+ private float mDurationF;
+
+ private float mAlpha;
+ private float mA, mB, mC, mD;
+
+ private boolean mCanceled;
+
+ public SmoothAnimator(float smoothness, int duration) {
+ mAlpha = smoothness;
+ mDurationF = (float) duration;
+ mA = mD = 1f / (2f * mAlpha * (1f - mAlpha));
+ mB = mAlpha / (2f * (mAlpha - 1f));
+ mC = 1f / (1f - mAlpha);
+ }
+
+ public float transform(float frac) {
+ if (frac < mAlpha) {
+ return mA * frac * frac;
+ } else if (frac < 1f - mAlpha) {
+ return mB + mC * frac;
+ } else {
+ return 1f - mD * (frac - 1f) * (frac - 1f);
+ }
+ }
+
+ public void start() {
+ mStartTime = SystemClock.uptimeMillis();
+ mCanceled = false;
+ onStart();
+ post(this);
+ }
+
+ public void cancel() {
+ mCanceled = true;
+ }
+
+ public void onStart() {
+ //stub
+ }
+
+ public void onUpdate(float frac, float smoothFrac) {
+ //stub
+ }
+
+ public void onStop() {
+ //stub
+ }
+
+ @Override
+ public void run() {
+ if (mCanceled) {
+ return;
+ }
+
+ float fraction = ((float) (SystemClock.uptimeMillis() - mStartTime)) / mDurationF;
+
+ if (fraction >= 1f) {
+ onUpdate(1f, 1f);
+ onStop();
+ } else {
+ onUpdate(fraction, transform(fraction));
+ post(this);
+ }
+ }
+ }
+
+ /**
+ * Centers floating View under touch point.
+ */
+ private class LiftAnimator extends SmoothAnimator {
+
+ private float mInitDragDeltaY;
+ private float mFinalDragDeltaY;
+
+ public LiftAnimator(float smoothness, int duration) {
+ super(smoothness, duration);
+ }
+
+ @Override
+ public void onStart() {
+ mInitDragDeltaY = mDragDeltaY;
+ mFinalDragDeltaY = mFloatViewHeightHalf;
+ }
+
+ @Override
+ public void onUpdate(float frac, float smoothFrac) {
+ if (mDragState != DRAGGING) {
+ cancel();
+ } else {
+ mDragDeltaY = (int) (smoothFrac * mFinalDragDeltaY + (1f - smoothFrac) * mInitDragDeltaY);
+ mFloatLoc.y = mY - mDragDeltaY;
+ doDragFloatView(true);
+ }
+ }
+ }
+
+ /**
+ * Centers floating View over drop slot before destroying.
+ */
+ private class DropAnimator extends SmoothAnimator {
+
+ private int mDropPos;
+ private int srcPos;
+ private float mInitDeltaY;
+ private float mInitDeltaX;
+
+ public DropAnimator(float smoothness, int duration) {
+ super(smoothness, duration);
+ }
+
+ @Override
+ public void onStart() {
+ mDropPos = mFloatPos;
+ srcPos = mSrcPos;
+ mDragState = DROPPING;
+ mInitDeltaY = mFloatLoc.y - getTargetY();
+ mInitDeltaX = mFloatLoc.x - getPaddingLeft();
+ }
+
+ private int getTargetY() {
+ final int first = getFirstVisiblePosition();
+ final int otherAdjust = (mItemHeightCollapsed + getDividerHeight()) / 2;
+ View v = getChildAt(mDropPos - first);
+ int targetY = -1;
+ if (v != null) {
+ if (mDropPos == srcPos) {
+ targetY = v.getTop();
+ } else if (mDropPos < srcPos) {
+ // expanded down
+ targetY = v.getTop() - otherAdjust;
+ } else {
+ // expanded up
+ targetY = v.getBottom() + otherAdjust - mFloatViewHeight;
+ }
+ } else {
+ // drop position is not on screen?? no animation
+ cancel();
+ }
+
+ return targetY;
+ }
+
+ @Override
+ public void onUpdate(float frac, float smoothFrac) {
+ final int targetY = getTargetY();
+ final float deltaY = mFloatLoc.y - targetY;
+ final float f = 1f - smoothFrac;
+ if (f < Math.abs(deltaY / mInitDeltaY)) {
+ mFloatLoc.y = targetY + (int) (mInitDeltaY * f);
+ mFloatLoc.x = getPaddingLeft() + (int) (mInitDeltaX * f);
+ doDragFloatView(true);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ dropFloatView();
+ }
+
+ }
+
+ /**
+ * Collapses expanded items.
+ */
+ private class RemoveAnimator extends SmoothAnimator {
+
+ private float mFirstStartBlank;
+ private float mSecondStartBlank;
+
+ private int mFirstChildHeight = -1;
+ private int mSecondChildHeight = -1;
+
+ private int mFirstPos;
+ private int mSecondPos;
+
+ public RemoveAnimator(float smoothness, int duration) {
+ super(smoothness, duration);
+ }
+
+ @Override
+ public void onStart() {
+ mFirstChildHeight = -1;
+ mSecondChildHeight = -1;
+ mFirstPos = mFirstExpPos;
+ mSecondPos = mSecondExpPos;
+ mDragState = REMOVING;
+ destroyFloatView();
+ }
+
+ @Override
+ public void onUpdate(float frac, float smoothFrac) {
+ float f = 1f - smoothFrac;
+
+ final int firstVis = getFirstVisiblePosition();
+ View item = getChildAt(mFirstPos - firstVis);
+ ViewGroup.LayoutParams lp;
+ int blank;
+ if (item != null) {
+ if (mFirstChildHeight == -1) {
+ mFirstChildHeight = getChildHeight(mFirstPos, item, false);
+ mFirstStartBlank = (float) (item.getHeight() - mFirstChildHeight);
+ }
+ blank = Math.max((int) (f * mFirstStartBlank), 1);
+ lp = item.getLayoutParams();
+ lp.height = mFirstChildHeight + blank;
+ item.setLayoutParams(lp);
+ }
+ if (mSecondPos != mFirstPos) {
+ item = getChildAt(mSecondPos - firstVis);
+ if (item != null) {
+ if (mSecondChildHeight == -1) {
+ mSecondChildHeight = getChildHeight(mSecondPos, item, false);
+ mSecondStartBlank = (float) (item.getHeight() - mSecondChildHeight);
+ }
+ blank = Math.max((int) (f * mSecondStartBlank), 1);
+ lp = item.getLayoutParams();
+ lp.height = mSecondChildHeight + blank;
+ item.setLayoutParams(lp);
+ }
+ }
+ }
+
+ @Override
+ public void onStop() {
+ doRemoveItem();
+ }
+ }
+
+ /**
+ * Removes an item from the list and animates the removal.
+ *
+ * @param which Position to remove (NOTE: headers/footers ignored!
+ * this is a position in your input ListAdapter).
+ */
+ public void removeItem(int which) {
+ if (mDragState == IDLE || mDragState == DRAGGING) {
+ if (mDragState == IDLE) {
+ // called from outside drag-sort
+ mSrcPos = getHeaderViewsCount() + which;
+ mFirstExpPos = mSrcPos;
+ mSecondExpPos = mSrcPos;
+ mFloatPos = mSrcPos;
+ View v = getChildAt(mSrcPos - getFirstVisiblePosition());
+ if (v != null) {
+ v.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ if (mInTouchEvent) {
+ switch (mCancelMethod) {
+ case ON_TOUCH_EVENT:
+ super.onTouchEvent(mCancelEvent);
+ break;
+ case ON_INTERCEPT_TOUCH_EVENT:
+ super.onInterceptTouchEvent(mCancelEvent);
+ break;
+ }
+ }
+
+ if (mRemoveAnimator != null) {
+ mRemoveAnimator.start();
+ } else {
+ doRemoveItem(which);
+ }
+ }
+ }
+
+
+ /**
+ * Move an item, bypassing the drag-sort process. Simply calls
+ * through to {@link DropListener#drop(int, int)}.
+ *
+ * @param from Position to move (NOTE: headers/footers ignored!
+ * this is a position in your input ListAdapter).
+ * @param to Target position (NOTE: headers/footers ignored!
+ * this is a position in your input ListAdapter).
+ */
+ public void moveItem(int from, int to) {
+ if (mDropListener != null) {
+ final int count = getInputAdapter().getCount();
+ if (from >= 0 && from < count && to >= 0 && to < count) {
+ mDropListener.drop(from, to);
+ }
+ }
+ }
+
+ /**
+ * Cancel a drag. Calls {@link #stopDrag(boolean, boolean)} with
+ * <code>true</code> as the first argument.
+ */
+ public void cancelDrag() {
+ if (mDragState == DRAGGING) {
+ mDragScroller.stopScrolling(true);
+ destroyFloatView();
+ clearPositions();
+ adjustAllItems();
+
+ if (mInTouchEvent) {
+ mDragState = STOPPED;
+ } else {
+ mDragState = IDLE;
+ }
+ }
+ }
+
+ private void clearPositions() {
+ mSrcPos = -1;
+ mFirstExpPos = -1;
+ mSecondExpPos = -1;
+ mFloatPos = -1;
+ }
+
+ private void dropFloatView() {
+ // must set to avoid cancelDrag being called from the
+ // DataSetObserver
+ mDragState = DROPPING;
+
+ if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) {
+ final int numHeaders = getHeaderViewsCount();
+ mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders);
+ }
+
+ destroyFloatView();
+
+ adjustOnReorder();
+ clearPositions();
+ adjustAllItems();
+
+ // now the drag is done
+ if (mInTouchEvent) {
+ mDragState = STOPPED;
+ } else {
+ mDragState = IDLE;
+ }
+ }
+
+ private void doRemoveItem() {
+ doRemoveItem(mSrcPos - getHeaderViewsCount());
+ }
+
+ /**
+ * Removes dragged item from the list. Calls RemoveListener.
+ */
+ private void doRemoveItem(int which) {
+ // must set to avoid cancelDrag being called from the
+ // DataSetObserver
+ mDragState = REMOVING;
+
+ // end it
+ if (mRemoveListener != null) {
+ mRemoveListener.remove(which);
+ }
+
+ destroyFloatView();
+
+ adjustOnReorder();
+ clearPositions();
+
+ // now the drag is done
+ if (mInTouchEvent) {
+ mDragState = STOPPED;
+ } else {
+ mDragState = IDLE;
+ }
+ }
+
+ private void adjustOnReorder() {
+ final int firstPos = getFirstVisiblePosition();
+ //Log.d("mobeta", "first="+firstPos+" src="+mSrcPos);
+ if (mSrcPos < firstPos) {
+ // collapsed src item is off screen;
+ // adjust the scroll after item heights have been fixed
+ View v = getChildAt(0);
+ int top = 0;
+ if (v != null) {
+ top = v.getTop();
+ }
+ //Log.d("mobeta", "top="+top+" fvh="+mFloatViewHeight);
+ setSelectionFromTop(firstPos - 1, top - getPaddingTop());
+ }
+ }
+
+ /**
+ * Stop a drag in progress. Pass <code>true</code> if you would
+ * like to remove the dragged item from the list.
+ *
+ * @param remove Remove the dragged item from the list. Calls
+ * a registered RemoveListener, if one exists. Otherwise, calls
+ * the DropListener, if one exists.
+ *
+ * @return True if the stop was successful. False if there is
+ * no floating View.
+ */
+ public boolean stopDrag(boolean remove) {
+ if (mFloatView != null) {
+ mDragScroller.stopScrolling(true);
+
+ if (remove) {
+ removeItem(mSrcPos - getHeaderViewsCount());
+ } else {
+ if (mDropAnimator != null) {
+ mDropAnimator.start();
+ } else {
+ dropFloatView();
+ }
+ }
+
+ if (mTrackDragSort) {
+ mDragSortTracker.stopTracking();
+ }
+
+ return true;
+ } else {
+ // stop failed
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mIgnoreTouchEvent) {
+ mIgnoreTouchEvent = false;
+ return false;
+ }
+
+ if (!mDragEnabled) {
+ return super.onTouchEvent(ev);
+ }
+
+ boolean more = false;
+
+ boolean lastCallWasIntercept = mLastCallWasIntercept;
+ mLastCallWasIntercept = false;
+
+ if (!lastCallWasIntercept) {
+ saveTouchCoords(ev);
+ }
+
+ //if (mFloatView != null) {
+ if (mDragState == DRAGGING) {
+ onDragTouchEvent(ev);
+ more = true; //give us more!
+ } else {
+ // what if float view is null b/c we dropped in middle
+ // of drag touch event?
+
+ //if (mDragState != STOPPED) {
+ if (mDragState == IDLE) {
+ if (super.onTouchEvent(ev)) {
+ more = true;
+ }
+ }
+
+ int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+ switch (action) {
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ doActionUpOrCancel();
+ break;
+ default:
+ if (more) {
+ mCancelMethod = ON_TOUCH_EVENT;
+ }
+ }
+ }
+
+ return more;
+ }
+
+ private void doActionUpOrCancel() {
+ mCancelMethod = NO_CANCEL;
+ mInTouchEvent = false;
+ if (mDragState == STOPPED) {
+ mDragState = IDLE;
+ }
+ mCurrFloatAlpha = mFloatAlpha;
+ mChildHeightCache.clear();
+ }
+
+ private void saveTouchCoords(MotionEvent ev) {
+ int action = ev.getAction() & MotionEvent.ACTION_MASK;
+ if (action != MotionEvent.ACTION_DOWN) {
+ mLastY = mY;
+ }
+ mX = (int) ev.getX();
+ mY = (int) ev.getY();
+ if (action == MotionEvent.ACTION_DOWN) {
+ mLastY = mY;
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (!mDragEnabled) {
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ saveTouchCoords(ev);
+ mLastCallWasIntercept = true;
+
+ int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ if (mDragState != IDLE) {
+ // intercept and ignore
+ mIgnoreTouchEvent = true;
+ return true;
+ }
+ mInTouchEvent = true;
+ }
+
+ boolean intercept = false;
+
+ // the following deals with calls to super.onInterceptTouchEvent
+ if (mFloatView != null) {
+ // super's touch event canceled in startDrag
+ intercept = true;
+ } else {
+ if (super.onInterceptTouchEvent(ev)) {
+ intercept = true;
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ doActionUpOrCancel();
+ break;
+ default:
+ if (intercept) {
+ mCancelMethod = ON_TOUCH_EVENT;
+ } else {
+ mCancelMethod = ON_INTERCEPT_TOUCH_EVENT;
+ }
+ }
+ }
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mInTouchEvent = false;
+ }
+
+ return intercept;
+ }
+
+ /**
+ * Set the width of each drag scroll region by specifying
+ * a fraction of the ListView height.
+ *
+ * @param heightFraction Fraction of ListView height. Capped at
+ * 0.5f.
+ *
+ */
+ public void setDragScrollStart(float heightFraction) {
+ setDragScrollStarts(heightFraction, heightFraction);
+ }
+
+ /**
+ * Set the width of each drag scroll region by specifying
+ * a fraction of the ListView height.
+ *
+ * @param upperFrac Fraction of ListView height for up-scroll bound.
+ * Capped at 0.5f.
+ * @param lowerFrac Fraction of ListView height for down-scroll bound.
+ * Capped at 0.5f.
+ *
+ */
+ public void setDragScrollStarts(float upperFrac, float lowerFrac) {
+ if (lowerFrac > 0.5f) {
+ mDragDownScrollStartFrac = 0.5f;
+ } else {
+ mDragDownScrollStartFrac = lowerFrac;
+ }
+
+ if (upperFrac > 0.5f) {
+ mDragUpScrollStartFrac = 0.5f;
+ } else {
+ mDragUpScrollStartFrac = upperFrac;
+ }
+
+ if (getHeight() != 0) {
+ updateScrollStarts();
+ }
+ }
+
+ private void continueDrag(int x, int y) {
+
+ // proposed position
+ mFloatLoc.x = x - mDragDeltaX;
+ mFloatLoc.y = y - mDragDeltaY;
+
+ doDragFloatView(true);
+
+ int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf);
+ int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf);
+
+ // get the current scroll direction
+ int currentScrollDir = mDragScroller.getScrollDir();
+
+ if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) {
+ // dragged down, it is below the down scroll start and it is not scrolling up
+
+ if (currentScrollDir != DragScroller.STOP) {
+ // moved directly from up scroll to down scroll
+ mDragScroller.stopScrolling(true);
+ }
+
+ // start scrolling down
+ mDragScroller.startScrolling(DragScroller.DOWN);
+ } else if (maxY < mLastY && maxY < mUpScrollStartY && currentScrollDir != DragScroller.UP) {
+ // dragged up, it is above the up scroll start and it is not scrolling up
+
+ if (currentScrollDir != DragScroller.STOP) {
+ // moved directly from down scroll to up scroll
+ mDragScroller.stopScrolling(true);
+ }
+
+ // start scrolling up
+ mDragScroller.startScrolling(DragScroller.UP);
+ }
+ else if (maxY >= mUpScrollStartY && minY <= mDownScrollStartY && mDragScroller.isScrolling()) {
+ // not in the upper nor in the lower drag-scroll regions but it is still scrolling
+
+ mDragScroller.stopScrolling(true);
+ }
+ }
+
+ private void updateScrollStarts() {
+ final int padTop = getPaddingTop();
+ final int listHeight = getHeight() - padTop - getPaddingBottom();
+ float heightF = (float) listHeight;
+
+ mUpScrollStartYF = padTop + mDragUpScrollStartFrac * heightF;
+ mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * heightF;
+
+ mUpScrollStartY = (int) mUpScrollStartYF;
+ mDownScrollStartY = (int) mDownScrollStartYF;
+
+ mDragUpScrollHeight = mUpScrollStartYF - padTop;
+ mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ updateScrollStarts();
+ }
+
+ private void adjustAllItems() {
+ final int first = getFirstVisiblePosition();
+ final int last = getLastVisiblePosition();
+
+ int begin = Math.max(0, getHeaderViewsCount() - first);
+ int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first);
+
+ for (int i = begin; i <= end; ++i) {
+ View v = getChildAt(i);
+ if (v != null) {
+ adjustItem(first + i, v, false);
+ }
+ }
+ }
+
+ /**
+ * Sets layout param height, gravity, and visibility on
+ * wrapped item.
+ */
+ private void adjustItem(int position, View v, boolean invalidChildHeight) {
+
+ // Adjust item height
+
+ int height = calcItemHeight(position, v, invalidChildHeight);
+ ViewGroup.LayoutParams lp = v.getLayoutParams();
+
+ if (height != lp.height) {
+ lp.height = height;
+ v.setLayoutParams(lp);
+ }
+
+ // Adjust item gravity
+
+ if (position == mFirstExpPos || position == mSecondExpPos) {
+ if (position < mSrcPos) {
+ ((DragSortItemView) v).setGravity(Gravity.BOTTOM);
+ } else if (position > mSrcPos) {
+ ((DragSortItemView) v).setGravity(Gravity.TOP);
+ }
+ }
+
+ // Finally adjust item visibility
+
+ int oldVis = v.getVisibility();
+ int vis = View.VISIBLE;
+
+ if (position == mSrcPos && mFloatView != null) {
+ vis = View.INVISIBLE;
+ }
+
+ if (vis != oldVis) {
+ v.setVisibility(vis);
+ }
+ }
+
+ private int getChildHeight(int position) {
+ if (position == mSrcPos) {
+ return 0;
+ }
+
+ View v = getChildAt(position - getFirstVisiblePosition());
+
+ if (v != null) {
+ // item is onscreen, therefore child height is valid,
+ // hence the "true"
+ return getChildHeight(position, v, false);
+ } else {
+ // item is offscreen
+ // first check cache for child height at this position
+ int childHeight = mChildHeightCache.get(position);
+ if (childHeight != -1) {
+ //Log.d("mobeta", "found child height in cache!");
+ return childHeight;
+ }
+
+ final ListAdapter adapter = getAdapter();
+ int type = adapter.getItemViewType(position);
+
+ // There might be a better place for checking for the following
+ final int typeCount = adapter.getViewTypeCount();
+ if (typeCount != mSampleViewTypes.length) {
+ mSampleViewTypes = new View[typeCount];
+ }
+
+ if (type >= 0) {
+ if (mSampleViewTypes[type] == null) {
+ v = adapter.getView(position, null, this);
+ mSampleViewTypes[type] = v;
+ } else {
+ v = adapter.getView(position, mSampleViewTypes[type], this);
+ }
+ } else {
+ // type is HEADER_OR_FOOTER or IGNORE
+ v = adapter.getView(position, null, this);
+ }
+
+ // current child height is invalid, hence "true" below
+ childHeight = getChildHeight(position, v, true);
+
+ // cache it because this could have been expensive
+ mChildHeightCache.add(position, childHeight);
+
+ return childHeight;
+ }
+ }
+
+ private int getChildHeight(int position, View item, boolean invalidChildHeight) {
+ if (position == mSrcPos) {
+ return 0;
+ }
+
+ View child;
+ if (position < getHeaderViewsCount() || position >= getCount() - getFooterViewsCount()) {
+ child = item;
+ } else {
+ child = ((ViewGroup) item).getChildAt(0);
+ }
+
+ ViewGroup.LayoutParams lp = child.getLayoutParams();
+
+ if (lp != null) {
+ if (lp.height > 0) {
+ return lp.height;
+ }
+ }
+
+ int childHeight = child.getHeight();
+
+ if (childHeight == 0 || invalidChildHeight) {
+ measureItem(child);
+ childHeight = child.getMeasuredHeight();
+ }
+
+ return childHeight;
+ }
+
+ private int calcItemHeight(int position, View item, boolean invalidChildHeight) {
+ return calcItemHeight(position, getChildHeight(position, item, invalidChildHeight));
+ }
+
+ private int calcItemHeight(int position, int childHeight) {
+
+ boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos;
+ int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
+ int slideHeight = (int) (mSlideFrac * maxNonSrcBlankHeight);
+
+ int height;
+
+ if (position == mSrcPos) {
+ if (mSrcPos == mFirstExpPos) {
+ if (isSliding) {
+ height = slideHeight + mItemHeightCollapsed;
+ } else {
+ height = mFloatViewHeight;
+ }
+ } else if (mSrcPos == mSecondExpPos) {
+ // if gets here, we know an item is sliding
+ height = mFloatViewHeight - slideHeight;
+ } else {
+ height = mItemHeightCollapsed;
+ }
+ } else if (position == mFirstExpPos) {
+ if (isSliding) {
+ height = childHeight + slideHeight;
+ } else {
+ height = childHeight + maxNonSrcBlankHeight;
+ }
+ } else if (position == mSecondExpPos) {
+ // we know an item is sliding (b/c 2ndPos != 1stPos)
+ height = childHeight + maxNonSrcBlankHeight - slideHeight;
+ } else {
+ height = childHeight;
+ }
+
+ return height;
+ }
+
+ @Override
+ public void requestLayout() {
+ if (!mBlockLayoutRequests) {
+ super.requestLayout();
+ }
+ }
+
+ private int adjustScroll(int movePos, View moveItem, int oldFirstExpPos, int oldSecondExpPos) {
+ int adjust = 0;
+
+ final int childHeight = getChildHeight(movePos);
+
+ int moveHeightBefore = moveItem.getHeight();
+ int moveHeightAfter = calcItemHeight(movePos, childHeight);
+
+ int moveBlankBefore = moveHeightBefore;
+ int moveBlankAfter = moveHeightAfter;
+ if (movePos != mSrcPos) {
+ moveBlankBefore -= childHeight;
+ moveBlankAfter -= childHeight;
+ }
+
+ int maxBlank = mFloatViewHeight;
+ if (mSrcPos != mFirstExpPos && mSrcPos != mSecondExpPos) {
+ maxBlank -= mItemHeightCollapsed;
+ }
+
+ if (movePos <= oldFirstExpPos) {
+ if (movePos > mFirstExpPos) {
+ adjust += maxBlank - moveBlankAfter;
+ }
+ } else if (movePos == oldSecondExpPos) {
+ if (movePos <= mFirstExpPos) {
+ adjust += moveBlankBefore - maxBlank;
+ } else if (movePos == mSecondExpPos) {
+ adjust += moveHeightBefore - moveHeightAfter;
+ } else {
+ adjust += moveBlankBefore;
+ }
+ } else {
+ if (movePos <= mFirstExpPos) {
+ adjust -= maxBlank;
+ } else if (movePos == mSecondExpPos) {
+ adjust -= moveBlankAfter;
+ }
+ }
+
+ return adjust;
+ }
+
+ private void measureItem(View item) {
+ ViewGroup.LayoutParams lp = item.getLayoutParams();
+ if (lp == null) {
+ lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+ int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft() + getListPaddingRight(), lp.width);
+ int hspec;
+ if (lp.height > 0) {
+ hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
+ } else {
+ hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+ item.measure(wspec, hspec);
+ }
+
+ private void measureFloatView() {
+ if (mFloatView != null) {
+ measureItem(mFloatView);
+ mFloatViewHeight = mFloatView.getMeasuredHeight();
+ mFloatViewHeightHalf = mFloatViewHeight / 2;
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ //Log.d("mobeta", "onMeasure called");
+ if (mFloatView != null) {
+ if (mFloatView.isLayoutRequested()) {
+ measureFloatView();
+ }
+ mFloatViewOnMeasured = true; //set to false after layout
+ }
+ mWidthMeasureSpec = widthMeasureSpec;
+ }
+
+ @Override
+ protected void layoutChildren() {
+ super.layoutChildren();
+
+ if (mFloatView != null) {
+ if (mFloatView.isLayoutRequested() && !mFloatViewOnMeasured) {
+ // Have to measure here when usual android measure
+ // pass is skipped. This happens during a drag-sort
+ // when layoutChildren is called directly.
+ measureFloatView();
+ }
+ mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight());
+ mFloatViewOnMeasured = false;
+ }
+ }
+
+
+ protected boolean onDragTouchEvent(MotionEvent ev) {
+ // we are in a drag
+ switch (ev.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_CANCEL:
+ if (mDragState == DRAGGING) {
+ cancelDrag();
+ }
+ doActionUpOrCancel();
+ break;
+ case MotionEvent.ACTION_UP:
+ //Log.d("mobeta", "calling stopDrag from onDragTouchEvent");
+ if (mDragState == DRAGGING) {
+ stopDrag(false);
+ }
+ doActionUpOrCancel();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ continueDrag((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Start a drag of item at <code>position</code> using the
+ * registered FloatViewManager. Calls through
+ * to {@link #startDrag(int,View,int,int,int)} after obtaining
+ * the floating View from the FloatViewManager.
+ *
+ * @param position Item to drag.
+ * @param dragFlags Flags that restrict some movements of the
+ * floating View. For example, set <code>dragFlags |=
+ * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating
+ * View in all directions except off the screen to the left.
+ * @param deltaX Offset in x of the touch coordinate from the
+ * left edge of the floating View (i.e. touch-x minus float View
+ * left).
+ * @param deltaY Offset in y of the touch coordinate from the
+ * top edge of the floating View (i.e. touch-y minus float View
+ * top).
+ *
+ * @return True if the drag was started, false otherwise. This
+ * <code>startDrag</code> will fail if we are not currently in
+ * a touch event, there is no registered FloatViewManager,
+ * or the FloatViewManager returns a null View.
+ */
+ public boolean startDrag(int position, int dragFlags, int deltaX, int deltaY) {
+ if (!mInTouchEvent || mFloatViewManager == null) {
+ return false;
+ }
+
+ View v = mFloatViewManager.onCreateFloatView(position);
+
+ if (v == null) {
+ return false;
+ } else {
+ return startDrag(position, v, dragFlags, deltaX, deltaY);
+ }
+
+ }
+
+ /**
+ * Start a drag of item at <code>position</code> without using
+ * a FloatViewManager.
+ *
+ * @param position Item to drag.
+ * @param floatView Floating View.
+ * @param dragFlags Flags that restrict some movements of the
+ * floating View. For example, set <code>dragFlags |=
+ * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating
+ * View in all directions except off the screen to the left.
+ * @param deltaX Offset in x of the touch coordinate from the
+ * left edge of the floating View (i.e. touch-x minus float View
+ * left).
+ * @param deltaY Offset in y of the touch coordinate from the
+ * top edge of the floating View (i.e. touch-y minus float View
+ * top).
+ *
+ * @return True if the drag was started, false otherwise. This
+ * <code>startDrag</code> will fail if we are not currently in
+ * a touch event, <code>floatView</code> is null, or there is
+ * a drag in progress.
+ */
+ public boolean startDrag(int position, View floatView, int dragFlags, int deltaX, int deltaY) {
+ if (mDragState != IDLE || !mInTouchEvent || mFloatView != null || floatView == null) {
+ return false;
+ }
+
+ if (getParent() != null) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+
+ int pos = position + getHeaderViewsCount();
+ mFirstExpPos = pos;
+ mSecondExpPos = pos;
+ mSrcPos = pos;
+ mFloatPos = pos;
+
+ //mDragState = dragType;
+ mDragState = DRAGGING;
+ mDragFlags = 0;
+ mDragFlags |= dragFlags;
+
+ mFloatView = floatView;
+ measureFloatView(); //sets mFloatViewHeight
+
+ mDragDeltaX = deltaX;
+ mDragDeltaY = deltaY;
+
+ //updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY);
+ mFloatLoc.x = mX - mDragDeltaX;
+ mFloatLoc.y = mY - mDragDeltaY;
+
+ // set src item invisible
+ final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition());
+
+ if (srcItem != null) {
+ srcItem.setVisibility(View.INVISIBLE);
+ }
+
+ if (mTrackDragSort) {
+ mDragSortTracker.startTracking();
+ }
+
+ // once float view is created, events are no longer passed
+ // to ListView
+ switch (mCancelMethod) {
+ case ON_TOUCH_EVENT:
+ super.onTouchEvent(mCancelEvent);
+ break;
+ case ON_INTERCEPT_TOUCH_EVENT:
+ super.onInterceptTouchEvent(mCancelEvent);
+ break;
+ }
+
+ requestLayout();
+
+ if (mLiftAnimator != null) {
+ mLiftAnimator.start();
+ }
+
+ return true;
+ }
+
+ private void doDragFloatView(boolean forceInvalidate) {
+ int movePos = getFirstVisiblePosition() + getChildCount() / 2;
+ View moveItem = getChildAt(getChildCount() / 2);
+
+ if (moveItem == null) {
+ return;
+ }
+
+ doDragFloatView(movePos, moveItem, forceInvalidate);
+ }
+
+ private void doDragFloatView(int movePos, View moveItem, boolean forceInvalidate) {
+ mBlockLayoutRequests = true;
+
+ updateFloatView();
+
+ int oldFirstExpPos = mFirstExpPos;
+ int oldSecondExpPos = mSecondExpPos;
+
+ boolean updated = updatePositions();
+
+ if (updated) {
+ adjustAllItems();
+ int scroll = adjustScroll(movePos, moveItem, oldFirstExpPos, oldSecondExpPos);
+ //Log.d("mobeta", " adjust scroll="+scroll);
+
+ setSelectionFromTop(movePos, moveItem.getTop() + scroll - getPaddingTop());
+ layoutChildren();
+ }
+
+ if (updated || forceInvalidate) {
+ invalidate();
+ }
+
+ mBlockLayoutRequests = false;
+ }
+
+ /**
+ * Sets float View location based on suggested values and
+ * constraints set in mDragFlags.
+ */
+ private void updateFloatView() {
+
+ if (mFloatViewManager != null) {
+ mTouchLoc.set(mX, mY);
+ mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, mTouchLoc);
+ }
+
+ final int floatX = mFloatLoc.x;
+ final int floatY = mFloatLoc.y;
+
+ // restrict x motion
+ int padLeft = getPaddingLeft();
+ if ((mDragFlags & DRAG_POS_X) == 0 && floatX > padLeft) {
+ mFloatLoc.x = padLeft;
+ } else if ((mDragFlags & DRAG_NEG_X) == 0 && floatX < padLeft) {
+ mFloatLoc.x = padLeft;
+ }
+
+ // keep floating view from going past bottom of last header view
+ final int numHeaders = getHeaderViewsCount();
+ final int numFooters = getFooterViewsCount();
+ final int firstPos = getFirstVisiblePosition();
+ final int lastPos = getLastVisiblePosition();
+
+ //Log.d("mobeta", "nHead="+numHeaders+" nFoot="+numFooters+" first="+firstPos+" last="+lastPos);
+ int topLimit = getPaddingTop();
+ if (firstPos < numHeaders) {
+ topLimit = getChildAt(numHeaders - firstPos - 1).getBottom();
+ }
+ if ((mDragFlags & DRAG_NEG_Y) == 0) {
+ if (firstPos <= mSrcPos) {
+ topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit);
+ }
+ }
+ // bottom limit is top of first footer View or
+ // bottom of last item in list
+ int bottomLimit = getHeight() - getPaddingBottom();
+ if (lastPos >= getCount() - numFooters - 1) {
+ bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom();
+ }
+ if ((mDragFlags & DRAG_POS_Y) == 0) {
+ if (lastPos >= mSrcPos) {
+ bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit);
+ }
+ }
+
+ //Log.d("mobeta", "dragView top=" + (y - mDragDeltaY));
+ //Log.d("mobeta", "limit=" + limit);
+ //Log.d("mobeta", "mDragDeltaY=" + mDragDeltaY);
+
+ if (floatY < topLimit) {
+ mFloatLoc.y = topLimit;
+ } else if (floatY + mFloatViewHeight > bottomLimit) {
+ mFloatLoc.y = bottomLimit - mFloatViewHeight;
+ }
+
+ // get y-midpoint of floating view (constrained to ListView bounds)
+ mFloatViewMid = mFloatLoc.y + mFloatViewHeightHalf;
+ }
+
+ private void destroyFloatView() {
+ if (mFloatView != null) {
+ mFloatView.setVisibility(GONE);
+ if (mFloatViewManager != null) {
+ mFloatViewManager.onDestroyFloatView(mFloatView);
+ }
+ mFloatView = null;
+ invalidate();
+ }
+ }
+
+ /**
+ * Interface for customization of the floating View appearance
+ * and dragging behavior. Implement
+ * your own and pass it to {@link #setFloatViewManager}. If
+ * your own is not passed, the default {@link SimpleFloatViewManager}
+ * implementation is used.
+ */
+ public interface FloatViewManager {
+ /**
+ * Return the floating View for item at <code>position</code>.
+ * DragSortListView will measure and layout this View for you,
+ * so feel free to just inflate it. You can help DSLV by
+ * setting some {@link ViewGroup.LayoutParams} on this View;
+ * otherwise it will set some for you (with a width of FILL_PARENT
+ * and a height of WRAP_CONTENT).
+ *
+ * @param position Position of item to drag (NOTE:
+ * <code>position</code> excludes header Views; thus, if you
+ * want to call {@link ListView#getChildAt(int)}, you will need
+ * to add {@link ListView#getHeaderViewsCount()} to the index).
+ *
+ * @return The View you wish to display as the floating View.
+ */
+ public View onCreateFloatView(int position);
+
+ /**
+ * Called whenever the floating View is dragged. Float View
+ * properties can be changed here. Also, the upcoming location
+ * of the float View can be altered by setting
+ * <code>location.x</code> and <code>location.y</code>.
+ *
+ * @param floatView The floating View.
+ * @param location The location (top-left; relative to DSLV
+ * top-left) at which the float
+ * View would like to appear, given the current touch location
+ * and the offset provided in {@link DragSortListView#startDrag}.
+ * @param touch The current touch location (relative to DSLV
+ * top-left).
+ * @param pendingScroll
+ */
+ public void onDragFloatView(View floatView, Point location, Point touch);
+
+ /**
+ * Called when the float View is dropped; lets you perform
+ * any necessary cleanup. The internal DSLV floating View
+ * reference is set to null immediately after this is called.
+ *
+ * @param floatView The floating View passed to
+ * {@link #onCreateFloatView(int)}.
+ */
+ public void onDestroyFloatView(View floatView);
+ }
+
+ public void setFloatViewManager(FloatViewManager manager) {
+ mFloatViewManager = manager;
+ }
+
+ public void setDragListener(DragListener l) {
+ mDragListener = l;
+ }
+
+ /**
+ * Allows for easy toggling between a DragSortListView
+ * and a regular old ListView. If enabled, items are
+ * draggable, where the drag init mode determines how
+ * items are lifted (see {@link setDragInitMode(int)}).
+ * If disabled, items cannot be dragged.
+ *
+ * @param enabled Set <code>true</code> to enable list
+ * item dragging
+ */
+ public void setDragEnabled(boolean enabled) {
+ mDragEnabled = enabled;
+ }
+
+ public boolean isDragEnabled() {
+ return mDragEnabled;
+ }
+
+ /**
+ * This better reorder your ListAdapter! DragSortListView does not do this
+ * for you; doesn't make sense to. Make sure
+ * {@link BaseAdapter#notifyDataSetChanged()} or something like it is
+ * called in your implementation.
+ *
+ * @param l
+ */
+ public void setDropListener(DropListener l) {
+ mDropListener = l;
+ }
+
+ /**
+ * Probably a no-brainer, but make sure that your remove listener
+ * calls {@link BaseAdapter#notifyDataSetChanged()} or something like it.
+ * When an item removal occurs, DragSortListView
+ * relies on a redraw of all the items to recover invisible views
+ * and such. Strictly speaking, if you remove something, your dataset
+ * has changed...
+ *
+ * @param l
+ */
+ public void setRemoveListener(RemoveListener l) {
+ mRemoveListener = l;
+ }
+
+ public interface DragListener {
+ public void drag(int from, int to);
+ }
+
+ /**
+ * Your implementation of this has to reorder your ListAdapter!
+ * Make sure to call
+ * {@link BaseAdapter#notifyDataSetChanged()} or something like it
+ * in your implementation.
+ *
+ * @author heycosmo
+ *
+ */
+ public interface DropListener {
+ public void drop(int from, int to);
+ }
+
+ /**
+ * Make sure to call
+ * {@link BaseAdapter#notifyDataSetChanged()} or something like it
+ * in your implementation.
+ *
+ * @author heycosmo
+ *
+ */
+ public interface RemoveListener {
+ public void remove(int which);
+ }
+
+ public interface DragSortListener extends DropListener, DragListener, RemoveListener {}
+
+ public void setDragSortListener(DragSortListener l) {
+ setDropListener(l);
+ setDragListener(l);
+ setRemoveListener(l);
+ }
+
+ /**
+ * Completely custom scroll speed profile. Default increases linearly
+ * with position and is constant in time. Create your own by implementing
+ * {@link DragSortListView.DragScrollProfile}.
+ *
+ * @param ssp
+ */
+ public void setDragScrollProfile(DragScrollProfile ssp) {
+ if (ssp != null) {
+ mScrollProfile = ssp;
+ }
+ }
+
+ /**
+ * Interface for controlling
+ * scroll speed as a function of touch position and time. Use
+ * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to
+ * set custom profile.
+ *
+ * @author heycosmo
+ *
+ */
+ public interface DragScrollProfile {
+ /**
+ * Return a scroll speed in pixels/millisecond. Always return a
+ * positive number.
+ *
+ * @param w Normalized position in scroll region (i.e. w \in [0,1]).
+ * Small w typically means slow scrolling.
+ * @param t Time (in milliseconds) since start of scroll (handy if you
+ * want scroll acceleration).
+ * @return Scroll speed at position w and time t in pixels/ms.
+ */
+ float getSpeed(float w, long t);
+ }
+
+ private class DragScroller implements Runnable {
+
+ private boolean mAbort;
+
+ private long mPrevTime;
+ private long mCurrTime;
+
+ private int dy;
+ private float dt;
+ private long tStart;
+ private int scrollDir;
+
+ public final static int STOP = -1;
+ public final static int UP = 0;
+ public final static int DOWN = 1;
+
+ private float mScrollSpeed; // pixels per ms
+
+ private boolean mScrolling = false;
+
+ public boolean isScrolling() {
+ return mScrolling;
+ }
+
+ public int getScrollDir() {
+ return mScrolling ? scrollDir : STOP;
+ }
+
+ public DragScroller() {}
+
+ public void startScrolling(int dir) {
+ if (!mScrolling) {
+ //Debug.startMethodTracing("dslv-scroll");
+ mAbort = false;
+ mScrolling = true;
+ tStart = SystemClock.uptimeMillis();
+ mPrevTime = tStart;
+ scrollDir = dir;
+ post(this);
+ }
+ }
+
+ public void stopScrolling(boolean now) {
+ if (now) {
+ DragSortListView.this.removeCallbacks(this);
+ mScrolling = false;
+ } else {
+ mAbort = true;
+ }
+
+ //Debug.stopMethodTracing();
+ }
+
+ @Override
+ public void run() {
+ if (mAbort) {
+ mScrolling = false;
+ return;
+ }
+
+ //Log.d("mobeta", "scroll");
+
+ final int first = getFirstVisiblePosition();
+ final int last = getLastVisiblePosition();
+ final int count = getCount();
+ final int padTop = getPaddingTop();
+ final int listHeight = getHeight() - padTop - getPaddingBottom();
+
+ int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf);
+ int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf);
+
+ if (scrollDir == UP) {
+ View v = getChildAt(0);
+ //Log.d("mobeta", "vtop="+v.getTop()+" padtop="+padTop);
+ if (v == null) {
+ mScrolling = false;
+ return;
+ } else {
+ if (first == 0 && v.getTop() == padTop) {
+ mScrolling = false;
+ return;
+ }
+ }
+ mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY) / mDragUpScrollHeight, mPrevTime);
+ } else {
+ View v = getChildAt(last - first);
+ if (v == null) {
+ mScrolling = false;
+ return;
+ } else {
+ if (last == count - 1 && v.getBottom() <= listHeight + padTop) {
+ mScrolling = false;
+ return;
+ }
+ }
+ mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF) / mDragDownScrollHeight, mPrevTime);
+ }
+
+ mCurrTime = SystemClock.uptimeMillis();
+ dt = (float) (mCurrTime - mPrevTime);
+
+ // dy is change in View position of a list item; i.e. positive dy
+ // means user is scrolling up (list item moves down the screen, remember
+ // y=0 is at top of View).
+ dy = (int) Math.round(mScrollSpeed * dt);
+
+ int movePos;
+ if (dy >= 0) {
+ dy = Math.min(listHeight, dy);
+ movePos = first;
+ } else {
+ dy = Math.max(-listHeight, dy);
+ movePos = last;
+ }
+
+ final View moveItem = getChildAt(movePos - first);
+ int top = moveItem.getTop() + dy;
+
+ if (movePos == 0 && top > padTop) {
+ top = padTop;
+ }
+
+ // always do scroll
+ mBlockLayoutRequests = true;
+
+ setSelectionFromTop(movePos, top - padTop);
+ DragSortListView.this.layoutChildren();
+ invalidate();
+
+ mBlockLayoutRequests = false;
+
+ // scroll means relative float View movement
+ doDragFloatView(movePos, moveItem, false);
+
+ mPrevTime = mCurrTime;
+ //Log.d("mobeta", " updated prevTime="+mPrevTime);
+
+ post(this);
+ }
+ }
+
+ private class DragSortTracker {
+ StringBuilder mBuilder = new StringBuilder();
+
+ File mFile;
+
+ private int mNumInBuffer = 0;
+ private int mNumFlushes = 0;
+
+ private boolean mTracking = false;
+
+ public DragSortTracker() {
+ File root = Environment.getExternalStorageDirectory();
+ mFile = new File(root, "dslv_state.txt");
+
+ if (!mFile.exists()) {
+ try {
+ mFile.createNewFile();
+ Log.d("mobeta", "file created");
+ } catch (IOException e) {
+ Log.w("mobeta", "Could not create dslv_state.txt");
+ Log.d("mobeta", e.getMessage());
+ }
+ }
+
+ }
+
+ public void startTracking() {
+ mBuilder.append("<DSLVStates>\n");
+ mNumFlushes = 0;
+ mTracking = true;
+ }
+
+ public void appendState() {
+ if (!mTracking) {
+ return;
+ }
+
+ mBuilder.append("<DSLVState>\n");
+ final int children = getChildCount();
+ final int first = getFirstVisiblePosition();
+ mBuilder.append(" <Positions>");
+ for (int i = 0; i < children; ++i) {
+ mBuilder.append(first + i).append(",");
+ }
+ mBuilder.append("</Positions>\n");
+
+ mBuilder.append(" <Tops>");
+ for (int i = 0; i < children; ++i) {
+ mBuilder.append(getChildAt(i).getTop()).append(",");
+ }
+ mBuilder.append("</Tops>\n");
+ mBuilder.append(" <Bottoms>");
+ for (int i = 0; i < children; ++i) {
+ mBuilder.append(getChildAt(i).getBottom()).append(",");
+ }
+ mBuilder.append("</Bottoms>\n");
+
+ mBuilder.append(" <FirstExpPos>").append(mFirstExpPos).append("</FirstExpPos>\n");
+ mBuilder.append(" <FirstExpBlankHeight>")
+ .append(getItemHeight(mFirstExpPos) - getChildHeight(mFirstExpPos))
+ .append("</FirstExpBlankHeight>\n");
+ mBuilder.append(" <SecondExpPos>").append(mSecondExpPos).append("</SecondExpPos>\n");
+ mBuilder.append(" <SecondExpBlankHeight>")
+ .append(getItemHeight(mSecondExpPos) - getChildHeight(mSecondExpPos))
+ .append("</SecondExpBlankHeight>\n");
+ mBuilder.append(" <SrcPos>").append(mSrcPos).append("</SrcPos>\n");
+ mBuilder.append(" <SrcHeight>").append(mFloatViewHeight + getDividerHeight()).append("</SrcHeight>\n");
+ mBuilder.append(" <ViewHeight>").append(getHeight()).append("</ViewHeight>\n");
+ mBuilder.append(" <LastY>").append(mLastY).append("</LastY>\n");
+ mBuilder.append(" <FloatY>").append(mFloatViewMid).append("</FloatY>\n");
+ mBuilder.append(" <ShuffleEdges>");
+ for (int i = 0; i < children; ++i) {
+ mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(",");
+ }
+ mBuilder.append("</ShuffleEdges>\n");
+
+ mBuilder.append("</DSLVState>\n");
+ mNumInBuffer++;
+
+ if (mNumInBuffer > 1000) {
+ flush();
+ mNumInBuffer = 0;
+ }
+ }
+
+ public void flush() {
+ if (!mTracking) {
+ return;
+ }
+
+ // save to file on sdcard
+ try {
+ boolean append = true;
+ if (mNumFlushes == 0) {
+ append = false;
+ }
+ FileWriter writer = new FileWriter(mFile, append);
+
+ writer.write(mBuilder.toString());
+ mBuilder.delete(0, mBuilder.length());
+
+ writer.flush();
+ writer.close();
+
+ mNumFlushes++;
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+
+ public void stopTracking() {
+ if (mTracking) {
+ mBuilder.append("</DSLVStates>\n");
+ flush();
+ mTracking = false;
+ }
+ }
+ }
+}
diff --git a/src/org/omnirom/omnigears/ui/dslv/SimpleFloatViewManager.java b/src/org/omnirom/omnigears/ui/dslv/SimpleFloatViewManager.java
new file mode 100644
index 0000000..8e523e6
--- /dev/null
+++ b/src/org/omnirom/omnigears/ui/dslv/SimpleFloatViewManager.java
@@ -0,0 +1,105 @@
+/**
+ * A subclass of the Android ListView component that enables drag
+ * and drop re-ordering of list items.
+ *
+ * Copyright 2012 Carl Bauer
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.omnirom.omnigears.ui.dslv;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ListView;
+
+/**
+ * Simple implementation of the FloatViewManager class. Uses list
+ * items as they appear in the ListView to create the floating View.
+ */
+public class SimpleFloatViewManager implements DragSortListView.FloatViewManager {
+
+ private Bitmap mFloatBitmap;
+
+ private ImageView mImageView;
+
+ private int mFloatBGColor = Color.BLACK;
+
+ protected ListView mListView;
+
+ public SimpleFloatViewManager(ListView lv) {
+ mListView = lv;
+ }
+
+ public void setBackgroundColor(int color) {
+ mFloatBGColor = color;
+ }
+
+ /**
+ * This simple implementation creates a Bitmap copy of the
+ * list item currently shown at ListView <code>position</code>.
+ */
+ @Override
+ public View onCreateFloatView(int position) {
+ // Guaranteed that this will not be null? I think so. Nope, got
+ // a NullPointerException once...
+ View v = mListView.getChildAt(position + mListView.getHeaderViewsCount() - mListView.getFirstVisiblePosition());
+
+ if (v == null) {
+ return null;
+ }
+
+ v.setPressed(false);
+
+ // Create a copy of the drawing cache so that it does not get
+ // recycled by the framework when the list tries to clean up memory
+ //v.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
+ v.setDrawingCacheEnabled(true);
+ mFloatBitmap = Bitmap.createBitmap(v.getDrawingCache());
+ v.setDrawingCacheEnabled(false);
+
+ if (mImageView == null) {
+ mImageView = new ImageView(mListView.getContext());
+ }
+ mImageView.setBackgroundColor(mFloatBGColor);
+ mImageView.setPadding(0, 0, 0, 0);
+ mImageView.setImageBitmap(mFloatBitmap);
+
+ return mImageView;
+ }
+
+ /**
+ * This does nothing
+ */
+ @Override
+ public void onDragFloatView(View floatView, Point position, Point touch) {
+ // do nothing
+ }
+
+ /**
+ * Removes the Bitmap from the ImageView created in
+ * onCreateFloatView() and tells the system to recycle it.
+ */
+ @Override
+ public void onDestroyFloatView(View floatView) {
+ ((ImageView) floatView).setImageDrawable(null);
+
+ mFloatBitmap.recycle();
+ mFloatBitmap = null;
+ }
+
+}
+