fixed update and enabling drag and drop to set a custom sort
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
[Google play link](https://play.google.com/store/apps/details?id=com.github.premnirmal.tickerwidget)
|
||||
- A homescreen widget that shows your stock portolio in a resizable grid.
|
||||
- Based off the [ministocks widget](https://github.com/niteshpatel/ministocks) but allowing unlimited tickers in a grid so you don't have to worry about sizing.
|
||||
- Stocks are sorted by change in percent (descending).
|
||||
- Stocks can be sorted by dragging and dropping the list.
|
||||
- Only performs automatic fetching of stocks during trading hours and weekdays.
|
||||
|
||||
## Importing and exporting
|
||||
@@ -19,7 +19,6 @@ The main purpose of me building this app is to practice usage of Square's Dagger
|
||||
- Ability to set a custom refresh interval
|
||||
- Ability to pick indices such as **^DJI**.
|
||||
- Ability to change the app theme and colors
|
||||
- Ability to change the way the stocks are sorted or to drag and drop the sort order.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+2
-2
@@ -8,8 +8,8 @@ android {
|
||||
applicationId "com.github.premnirmal.tickerwidget"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 21
|
||||
versionCode 5
|
||||
versionName "2.00.00"
|
||||
versionCode 6
|
||||
versionName "2.10.00"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
<activity android:name=".settings.SettingsActivity"
|
||||
android:label="@string/action_Settings"/>
|
||||
|
||||
<receiver android:name=".UpdateReceiver" android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.github.premnirmal.ticker.UPDATE"></action>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="com.github.premnirmal.ticker.widget.StockWidget">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
|
||||
@@ -9,9 +9,11 @@ import android.os.SystemClock;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeConstants;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.MutableDateTime;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by premnirmal on 12/22/14.
|
||||
@@ -74,9 +76,10 @@ public final class Tools {
|
||||
final int hourOfDay = DateTime.now().hourOfDay().get();
|
||||
if(hourOfDay >= 17) { // 5pm
|
||||
final MutableDateTime mutableDateTime = new MutableDateTime(DateTime.now());
|
||||
mutableDateTime.setZone(DateTimeZone.getDefault());
|
||||
mutableDateTime.addDays(1);
|
||||
mutableDateTime.setHourOfDay(9); // 9am
|
||||
mutableDateTime.setMinuteOfHour(5); // update at 9:05am
|
||||
mutableDateTime.setMinuteOfHour(15); // update at 9:15am
|
||||
if(mutableDateTime.getDayOfWeek() > DateTimeConstants.FRIDAY) {
|
||||
switch (mutableDateTime.getDayOfWeek()) {
|
||||
case DateTimeConstants.SATURDAY:
|
||||
@@ -93,4 +96,14 @@ public final class Tools {
|
||||
return SystemClock.elapsedRealtime() + (AlarmManager.INTERVAL_FIFTEEN_MINUTES * 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static String toCommaSeparatedString(List<String> list) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for(String string : list) {
|
||||
builder.append(string);
|
||||
builder.append(",");
|
||||
}
|
||||
builder.deleteCharAt(builder.length()-1);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.github.premnirmal.ticker;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.github.premnirmal.ticker.model.IStocksProvider;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Created by premnirmal on 12/23/14.
|
||||
*/
|
||||
public class UpdateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Inject
|
||||
IStocksProvider stocksProvider;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
((StocksApp)context.getApplicationContext()).inject(this);
|
||||
stocksProvider.fetch();
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ public interface IStocksProvider {
|
||||
|
||||
Collection<Stock> getStocks();
|
||||
|
||||
Collection<Stock> rearrange(List<String> tickers);
|
||||
|
||||
List<String> getTickers();
|
||||
|
||||
String lastFetched();
|
||||
|
||||
@@ -8,12 +8,13 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.github.premnirmal.ticker.R;
|
||||
import com.github.premnirmal.ticker.Tools;
|
||||
import com.github.premnirmal.ticker.events.NoNetworkEvent;
|
||||
import com.github.premnirmal.ticker.UpdateReceiver;
|
||||
import com.github.premnirmal.ticker.events.StockUpdatedEvent;
|
||||
import com.github.premnirmal.ticker.network.Stock;
|
||||
import com.github.premnirmal.ticker.network.StockQuery;
|
||||
@@ -25,8 +26,10 @@ import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -46,9 +49,12 @@ import rx.schedulers.Schedulers;
|
||||
public class StocksProvider implements IStocksProvider {
|
||||
|
||||
public static final String STOCK_LIST = "STOCK_LIST";
|
||||
public static final String SORTED_STOCK_LIST = "SORTED_STOCK_LIST";
|
||||
public static final String LAST_FETCHED = "LAST_FETCHED";
|
||||
|
||||
private static final Set<String> DEFAULT_LIST = new HashSet<String>() {
|
||||
public static final String UPDATE_FILTER = "com.github.premnirmal.ticker.UPDATE";
|
||||
|
||||
private static final Set<String> DEFAULT_SET = new HashSet<String>() {
|
||||
{
|
||||
add("^SPY");
|
||||
add("GOOG");
|
||||
@@ -57,7 +63,10 @@ public class StocksProvider implements IStocksProvider {
|
||||
}
|
||||
};
|
||||
|
||||
private Set<String> tickerList;
|
||||
private static final String DEFAULT_STOCKS = "^SPY,GOOG,AAPL,MSFT";
|
||||
|
||||
private Set<String> deprecatedTickerSet;
|
||||
private List<String> tickerList;
|
||||
private List<Stock> stockList;
|
||||
private String lastFetched;
|
||||
private final SharedPreferences preferences;
|
||||
@@ -71,16 +80,26 @@ public class StocksProvider implements IStocksProvider {
|
||||
this.api = api;
|
||||
this.context = context;
|
||||
preferences = context.getSharedPreferences(Tools.PREFS_NAME, Context.MODE_PRIVATE);
|
||||
tickerList = preferences.getStringSet(STOCK_LIST, DEFAULT_LIST);
|
||||
final String tickerListVars = preferences.getString(SORTED_STOCK_LIST, DEFAULT_STOCKS);
|
||||
tickerList = new ArrayList<>(Arrays.asList(tickerListVars.split(",")));
|
||||
if (preferences.contains(STOCK_LIST)) { // for users using older versions
|
||||
deprecatedTickerSet = preferences.getStringSet(STOCK_LIST, DEFAULT_SET);
|
||||
preferences.edit().remove(STOCK_LIST).commit();
|
||||
for (String ticker : deprecatedTickerSet) {
|
||||
if (!tickerList.contains(ticker)) {
|
||||
tickerList.add(ticker);
|
||||
}
|
||||
}
|
||||
preferences.edit().putString(SORTED_STOCK_LIST, Tools.toCommaSeparatedString(tickerList)).commit();
|
||||
}
|
||||
lastFetched = preferences.getString(LAST_FETCHED, null);
|
||||
tickerList.addAll(DEFAULT_LIST);
|
||||
fetch();
|
||||
}
|
||||
|
||||
private void save() {
|
||||
preferences.edit().remove(STOCK_LIST).commit();
|
||||
preferences.edit().putStringSet(STOCK_LIST, tickerList).commit();
|
||||
preferences.edit().putString(LAST_FETCHED, lastFetched).commit();
|
||||
preferences.edit().remove(STOCK_LIST)
|
||||
.putString(SORTED_STOCK_LIST, Tools.toCommaSeparatedString(tickerList))
|
||||
.putString(LAST_FETCHED, lastFetched).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,7 +127,7 @@ public class StocksProvider implements IStocksProvider {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
bus.post(new NoNetworkEvent());
|
||||
scheduleUpdate(SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_FIFTEEN_MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,16 +142,23 @@ public class StocksProvider implements IStocksProvider {
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
|
||||
context.sendBroadcast(intent);
|
||||
bus.post(new StockUpdatedEvent());
|
||||
scheduleUpdate(Tools.getMsToNextAlarm());
|
||||
}
|
||||
|
||||
private void scheduleUpdate(long msToNextAlarm) {
|
||||
final Intent updateReceiverIntent = new Intent(context, UpdateReceiver.class);
|
||||
updateReceiverIntent.setAction(UPDATE_FILTER);
|
||||
final AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
final PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, intent, 0);
|
||||
alarmManager.cancel(pendingIntent);
|
||||
final PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, updateReceiverIntent, 0);
|
||||
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||
Tools.getMsToNextAlarm(), pendingIntent);
|
||||
msToNextAlarm, pendingIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> addStock(String ticker) {
|
||||
if (tickerList.contains(ticker)) {
|
||||
return tickerList;
|
||||
}
|
||||
tickerList.add(ticker);
|
||||
save();
|
||||
fetch();
|
||||
@@ -141,7 +167,11 @@ public class StocksProvider implements IStocksProvider {
|
||||
|
||||
@Override
|
||||
public Collection<String> addStocks(Collection<String> tickers) {
|
||||
tickerList.addAll(tickers);
|
||||
for (String ticker : tickers) {
|
||||
if (!tickerList.contains(ticker)) {
|
||||
tickerList.add(ticker);
|
||||
}
|
||||
}
|
||||
save();
|
||||
fetch();
|
||||
return tickerList;
|
||||
@@ -163,11 +193,28 @@ public class StocksProvider implements IStocksProvider {
|
||||
@Override
|
||||
public Collection<Stock> getStocks() {
|
||||
if (stockList != null) {
|
||||
Collections.sort(stockList);
|
||||
sortStockList();
|
||||
}
|
||||
return stockList;
|
||||
}
|
||||
|
||||
private void sortStockList() {
|
||||
Collections.sort(stockList, new Comparator<Stock>() {
|
||||
@Override
|
||||
public int compare(Stock lhs, Stock rhs) {
|
||||
return ((Integer) stockList.indexOf(lhs.symbol))
|
||||
.compareTo(stockList.indexOf(rhs.symbol));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Stock> rearrange(List<String> tickers) {
|
||||
tickerList = new ArrayList<>(tickers);
|
||||
save();
|
||||
return getStocks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTickers() {
|
||||
return new ArrayList<>(tickerList);
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.github.premnirmal.ticker.network;
|
||||
import android.content.Context;
|
||||
|
||||
import com.github.premnirmal.ticker.R;
|
||||
import com.github.premnirmal.ticker.UpdateReceiver;
|
||||
import com.github.premnirmal.ticker.model.IStocksProvider;
|
||||
import com.github.premnirmal.ticker.model.StocksProvider;
|
||||
import com.github.premnirmal.ticker.settings.SettingsActivity;
|
||||
@@ -28,6 +29,7 @@ import retrofit.RestAdapter;
|
||||
RemoteStockViewAdapter.class,
|
||||
SettingsActivity.class,
|
||||
StockWidget.class,
|
||||
UpdateReceiver.class,
|
||||
ParanormalActivity.class
|
||||
},
|
||||
complete = false,
|
||||
|
||||
@@ -21,18 +21,20 @@ public class Stock implements Comparable<Stock> {
|
||||
}
|
||||
|
||||
static double getChangeFromPercentString(String percentString) {
|
||||
if(percentString == null) {
|
||||
if (percentString == null) {
|
||||
return -1000000.0d;
|
||||
}
|
||||
return Double.valueOf(percentString.replace('%','\0').trim());
|
||||
return Double.valueOf(percentString.replace('%', '\0').trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(o instanceof Stock) {
|
||||
if(symbol != null) {
|
||||
return symbol.replace("^","").equalsIgnoreCase(((Stock)o).symbol);
|
||||
if (o instanceof Stock) {
|
||||
if (symbol != null) {
|
||||
return symbol.replace("^", "").equalsIgnoreCase(((Stock) o).symbol.replace("^", ""));
|
||||
}
|
||||
} else if (o instanceof String) {
|
||||
return ((String) o).replace("^","").equalsIgnoreCase(symbol.replace("^", ""));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.Arrays;
|
||||
/**
|
||||
* Created by premnirmal on 12/22/14.
|
||||
*/
|
||||
public class FileImportTask extends AsyncTask<String, Void, Boolean> {
|
||||
class FileImportTask extends AsyncTask<String, Void, Boolean> {
|
||||
|
||||
private final IStocksProvider stocksProvider;
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.devpaul.filepickerlibrary.FilePickerActivity;
|
||||
import com.github.premnirmal.ticker.BuildConfig;
|
||||
import com.github.premnirmal.ticker.R;
|
||||
import com.github.premnirmal.ticker.StocksApp;
|
||||
import com.github.premnirmal.ticker.Tools;
|
||||
@@ -95,6 +97,8 @@ public class SettingsActivity extends ActionBarActivity {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
((TextView)findViewById(R.id.version)).setText("Version " + BuildConfig.VERSION_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.github.premnirmal.ticker.model.IStocksProvider;
|
||||
import com.github.premnirmal.ticker.settings.SettingsActivity;
|
||||
import com.github.premnirmal.ticker.widget.StockWidget;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.terlici.dragndroplist.DragNDropListView;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -88,9 +89,9 @@ public class ParanormalActivity extends ActionBarActivity {
|
||||
}, 600);
|
||||
}
|
||||
|
||||
final AdapterView adapterView = (AdapterView) findViewById(R.id.stockList);
|
||||
final DragNDropListView adapterView = (DragNDropListView) findViewById(R.id.stockList);
|
||||
final StocksAdapter adapter = new StocksAdapter(stocksProvider);
|
||||
adapterView.setAdapter(adapter);
|
||||
adapterView.setDragNDropAdapter(adapter);
|
||||
adapterView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
|
||||
|
||||
@@ -11,6 +11,8 @@ import android.widget.TextView;
|
||||
import com.github.premnirmal.ticker.R;
|
||||
import com.github.premnirmal.ticker.model.IStocksProvider;
|
||||
import com.github.premnirmal.ticker.network.Stock;
|
||||
import com.terlici.dragndroplist.DragNDropAdapter;
|
||||
import com.terlici.dragndroplist.DragNDropListView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -18,11 +20,13 @@ import java.util.List;
|
||||
/**
|
||||
* Created by premnirmal on 12/21/14.
|
||||
*/
|
||||
class StocksAdapter extends BaseAdapter {
|
||||
class StocksAdapter extends BaseAdapter implements DragNDropAdapter {
|
||||
|
||||
final List<Stock> stockList;
|
||||
private final List<Stock> stockList;
|
||||
private final IStocksProvider stocksProvider;
|
||||
|
||||
StocksAdapter(IStocksProvider stocksProvider) {
|
||||
this.stocksProvider = stocksProvider;
|
||||
stockList = stocksProvider.getStocks() == null ? new ArrayList<Stock>()
|
||||
: new ArrayList<>(stocksProvider.getStocks());
|
||||
}
|
||||
@@ -39,7 +43,7 @@ class StocksAdapter extends BaseAdapter {
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,4 +85,25 @@ class StocksAdapter extends BaseAdapter {
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDragHandler() {
|
||||
return R.id.dragHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDrag(DragNDropListView parent, View view, int position, long id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDrop(DragNDropListView parent, View view, int startPosition, int endPosition, long id) {
|
||||
stockList.add(endPosition, stockList.remove(startPosition));
|
||||
final List<String> newTickerList = new ArrayList<>();
|
||||
for(Stock stock : stockList) {
|
||||
newTickerList.add(stock.symbol);
|
||||
}
|
||||
stocksProvider.rearrange(newTickerList);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2012 Terlici Ltd.
|
||||
*
|
||||
* 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 com.terlici.dragndroplist;
|
||||
|
||||
import android.widget.ListAdapter;
|
||||
|
||||
public interface DragNDropAdapter extends ListAdapter, DragNDropListView.OnItemDragNDropListener {
|
||||
public int getDragHandler();
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2012 Terlici Ltd.
|
||||
*
|
||||
* 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 com.terlici.dragndroplist;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.SimpleCursorAdapter;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class DragNDropCursorAdapter extends SimpleCursorAdapter implements DragNDropAdapter {
|
||||
int mPosition[];
|
||||
int mHandler;
|
||||
|
||||
public DragNDropCursorAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to, int handler) {
|
||||
super(context, layout, cursor, from, to, 0);
|
||||
|
||||
mHandler = handler;
|
||||
setup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor c) {
|
||||
Cursor cursor = super.swapCursor(c);
|
||||
|
||||
mPosition = null;
|
||||
setup();
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
Cursor c = getCursor();
|
||||
|
||||
if (c == null || !c.moveToFirst()) return;
|
||||
|
||||
mPosition = new int[c.getCount()];
|
||||
|
||||
for (int i = 0; i < mPosition.length; ++i) mPosition[i] = i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View view, ViewGroup group) {
|
||||
return super.getDropDownView(mPosition[position], view, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return super.getItem(mPosition[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return super.getItemViewType(mPosition[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return super.getItemId(mPosition[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup group) {
|
||||
return super.getView(mPosition[position], view, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return super.isEnabled(mPosition[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDrag(DragNDropListView parent, View view, int position, long id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDrop(DragNDropListView parent, View view, int startPosition, int endPosition, long id) {
|
||||
int position = mPosition[startPosition];
|
||||
|
||||
if (startPosition < endPosition)
|
||||
for(int i = startPosition; i < endPosition; ++i)
|
||||
mPosition[i] = mPosition[i + 1];
|
||||
else if (endPosition < startPosition)
|
||||
for(int i = startPosition; i > endPosition; --i)
|
||||
mPosition[i] = mPosition[i - 1];
|
||||
|
||||
mPosition[endPosition] = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDragHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright 2012 Terlici Ltd.
|
||||
*
|
||||
* 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 com.terlici.dragndroplist;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Adapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.WrapperListAdapter;
|
||||
|
||||
public class DragNDropListView extends ListView {
|
||||
|
||||
public static interface OnItemDragNDropListener {
|
||||
public void onItemDrag(DragNDropListView parent, View view, int position, long id);
|
||||
public void onItemDrop(DragNDropListView parent, View view, int startPosition, int endPosition, long id);
|
||||
}
|
||||
|
||||
boolean mDragMode;
|
||||
|
||||
WindowManager mWm;
|
||||
int mStartPosition = INVALID_POSITION;
|
||||
int mDragPointOffset; // Used to adjust drag view location
|
||||
int mDragHandler = 0;
|
||||
|
||||
ImageView mDragView;
|
||||
|
||||
OnItemDragNDropListener mDragNDropListener;
|
||||
|
||||
private void init() {
|
||||
mWm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
|
||||
}
|
||||
|
||||
public DragNDropListView(Context context) {
|
||||
super(context);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public DragNDropListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public DragNDropListView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public void setOnItemDragNDropListener(OnItemDragNDropListener listener) {
|
||||
mDragNDropListener = listener;
|
||||
}
|
||||
|
||||
public void setDragNDropAdapter(DragNDropAdapter adapter) {
|
||||
mDragHandler = adapter.getDragHandler();
|
||||
setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the motion event was inside a handler view.
|
||||
*
|
||||
* @param ev
|
||||
* @return true if it is a dragging move, false otherwise.
|
||||
*/
|
||||
public boolean isDrag(MotionEvent ev) {
|
||||
if (mDragMode) return true;
|
||||
if (mDragHandler == 0) return false;
|
||||
|
||||
int x = (int)ev.getX();
|
||||
int y = (int)ev.getY();
|
||||
|
||||
int startposition = pointToPosition(x,y);
|
||||
|
||||
if (startposition == INVALID_POSITION) return false;
|
||||
|
||||
int childposition = startposition - getFirstVisiblePosition();
|
||||
View parent = getChildAt(childposition);
|
||||
View handler = parent.findViewById(mDragHandler);
|
||||
|
||||
if (handler == null) return false;
|
||||
|
||||
int top = parent.getTop() + handler.getTop();
|
||||
int bottom = top + handler.getHeight();
|
||||
int left = parent.getLeft() + handler.getLeft();
|
||||
int right = left + handler.getWidth();
|
||||
|
||||
return left <= x && x <= right && top <= y && y <= bottom;
|
||||
}
|
||||
|
||||
public boolean isDragging() {
|
||||
return mDragMode;
|
||||
}
|
||||
|
||||
public View getDragView() {
|
||||
return mDragView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
final int action = ev.getAction();
|
||||
final int x = (int)ev.getX();
|
||||
final int y = (int)ev.getY();
|
||||
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN && isDrag(ev)) mDragMode = true;
|
||||
|
||||
if (!mDragMode) return super.onTouchEvent(ev);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mStartPosition = pointToPosition(x, y);
|
||||
|
||||
if (mStartPosition != INVALID_POSITION) {
|
||||
int childPosition = mStartPosition - getFirstVisiblePosition();
|
||||
mDragPointOffset = y - getChildAt(childPosition).getTop();
|
||||
mDragPointOffset -= ((int)ev.getRawY()) - y;
|
||||
|
||||
startDrag(childPosition, y);
|
||||
drag(0, y);
|
||||
}
|
||||
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
drag(0, y);
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
default:
|
||||
mDragMode = false;
|
||||
|
||||
|
||||
if (mStartPosition != INVALID_POSITION) {
|
||||
// check if the position is a header/footer
|
||||
int actualPosition = pointToPosition(x,y);
|
||||
if (actualPosition > (getCount() - getFooterViewsCount()) - 1)
|
||||
actualPosition = INVALID_POSITION;
|
||||
|
||||
stopDrag(mStartPosition - getFirstVisiblePosition(), actualPosition);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the drag view.
|
||||
*
|
||||
* @param childIndex
|
||||
* @param y
|
||||
*/
|
||||
private void startDrag(int childIndex, int y) {
|
||||
View item = getChildAt(childIndex);
|
||||
|
||||
if (item == null) return;
|
||||
|
||||
long id = getItemIdAtPosition(mStartPosition);
|
||||
|
||||
if (mDragNDropListener != null)
|
||||
mDragNDropListener.onItemDrag(this, item, mStartPosition, id);
|
||||
|
||||
Adapter adapter = getAdapter();
|
||||
DragNDropAdapter dndAdapter;
|
||||
|
||||
// if exists a footer/header we have our adapter wrapped
|
||||
if (adapter instanceof WrapperListAdapter) {
|
||||
dndAdapter = (DragNDropAdapter)((WrapperListAdapter)adapter).getWrappedAdapter();
|
||||
} else {
|
||||
dndAdapter = (DragNDropAdapter)adapter;
|
||||
}
|
||||
|
||||
dndAdapter.onItemDrag(this, item, mStartPosition, id);
|
||||
|
||||
item.setDrawingCacheEnabled(true);
|
||||
|
||||
// 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
|
||||
Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache());
|
||||
|
||||
WindowManager.LayoutParams mWindowParams = new WindowManager.LayoutParams();
|
||||
mWindowParams.gravity = Gravity.TOP;
|
||||
mWindowParams.x = 0;
|
||||
mWindowParams.y = y - mDragPointOffset;
|
||||
|
||||
mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|
||||
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
|
||||
mWindowParams.format = PixelFormat.TRANSLUCENT;
|
||||
mWindowParams.windowAnimations = 0;
|
||||
|
||||
Context context = getContext();
|
||||
ImageView v = new ImageView(context);
|
||||
v.setImageBitmap(bitmap);
|
||||
|
||||
mWm.addView(v, mWindowParams);
|
||||
mDragView = v;
|
||||
|
||||
item.setVisibility(View.INVISIBLE);
|
||||
item.invalidate(); // We have not changed anything else.
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all dragging resources.
|
||||
*
|
||||
* @param childIndex
|
||||
*/
|
||||
private void stopDrag(int childIndex, int endPosition) {
|
||||
if (mDragView == null) return;
|
||||
|
||||
View item = getChildAt(childIndex);
|
||||
|
||||
if (endPosition != INVALID_POSITION) {
|
||||
long id = getItemIdAtPosition(mStartPosition);
|
||||
|
||||
if (mDragNDropListener != null)
|
||||
mDragNDropListener.onItemDrop(this, item, mStartPosition, endPosition, id);
|
||||
|
||||
Adapter adapter = getAdapter();
|
||||
DragNDropAdapter dndAdapter;
|
||||
|
||||
// if exists a footer/header we have our adapter wrapped
|
||||
if (adapter instanceof WrapperListAdapter) {
|
||||
dndAdapter = (DragNDropAdapter)((WrapperListAdapter)adapter).getWrappedAdapter();
|
||||
} else {
|
||||
dndAdapter = (DragNDropAdapter)adapter;
|
||||
}
|
||||
|
||||
dndAdapter.onItemDrop(this, item, mStartPosition, endPosition, id);
|
||||
}
|
||||
|
||||
mDragView.setVisibility(GONE);
|
||||
mWm.removeView(mDragView);
|
||||
|
||||
mDragView.setImageDrawable(null);
|
||||
mDragView = null;
|
||||
|
||||
item.setDrawingCacheEnabled(false);
|
||||
item.destroyDrawingCache();
|
||||
|
||||
item.setVisibility(View.VISIBLE);
|
||||
|
||||
mStartPosition = INVALID_POSITION;
|
||||
|
||||
invalidateViews(); // We have changed the adapter data, so change everything
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the drag view.
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
private void drag(int x, int y) {
|
||||
if (mDragView == null) return;
|
||||
|
||||
WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams)mDragView.getLayoutParams();
|
||||
layoutParams.x = x;
|
||||
layoutParams.y = y - mDragPointOffset;
|
||||
|
||||
mWm.updateViewLayout(mDragView, layoutParams);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2012 Terlici Ltd.
|
||||
*
|
||||
* 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 com.terlici.dragndroplist;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SimpleAdapter;
|
||||
|
||||
public class DragNDropSimpleAdapter extends SimpleAdapter implements DragNDropAdapter {
|
||||
int mPosition[];
|
||||
int mHandler;
|
||||
|
||||
public DragNDropSimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to, int handler) {
|
||||
super(context, data, resource, from, to);
|
||||
|
||||
mHandler = handler;
|
||||
setup(data.size());
|
||||
}
|
||||
|
||||
private void setup(int size) {
|
||||
mPosition = new int[size];
|
||||
|
||||
for (int i = 0; i < size; ++i) mPosition[i] = i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View view, ViewGroup group) {
|
||||
return super.getDropDownView(mPosition[position], view, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return super.getItem(mPosition[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return super.getItemViewType(mPosition[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return super.getItemId(mPosition[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup group) {
|
||||
return super.getView(mPosition[position], view, group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return super.isEnabled(mPosition[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDrag(DragNDropListView parent, View view, int position, long id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDrop(DragNDropListView parent, View view, int startPosition, int endPosition, long id) {
|
||||
int position = mPosition[startPosition];
|
||||
|
||||
if (startPosition < endPosition)
|
||||
for(int i = startPosition; i < endPosition; ++i)
|
||||
mPosition[i] = mPosition[i + 1];
|
||||
else if (endPosition < startPosition)
|
||||
for(int i = startPosition; i > endPosition; --i)
|
||||
mPosition[i] = mPosition[i - 1];
|
||||
|
||||
mPosition[endPosition] = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDragHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,12 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<GridView
|
||||
<com.terlici.dragndroplist.DragNDropListView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:scrollbars="none"
|
||||
android:numColumns="2"
|
||||
android:horizontalSpacing="10dp"
|
||||
android:id="@+id/stockList"
|
||||
/>
|
||||
|
||||
@@ -48,4 +48,15 @@
|
||||
|
||||
<include layout="@layout/divider"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/version"
|
||||
android:linksClickable="true"
|
||||
android:autoLink="all"
|
||||
/>
|
||||
|
||||
<include layout="@layout/divider"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -7,50 +8,76 @@
|
||||
android:paddingRight="5dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:id="@+id/row"
|
||||
>
|
||||
android:id="@+id/row">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="0.5"
|
||||
android:layout_weight="0.2"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:textSize="16sp"
|
||||
android:singleLine="true"
|
||||
android:textStyle="bold"
|
||||
tools:text="GOOG"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:id="@+id/ticker" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="0.5"
|
||||
android:layout_weight="0.3"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:textSize="16sp"
|
||||
android:singleLine="true"
|
||||
tools:text="GOOG"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:id="@+id/totalValue" />
|
||||
|
||||
<ViewFlipper
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="0.5"
|
||||
android:layout_weight="0.2"
|
||||
android:layout_height="wrap_content"
|
||||
android:flipInterval="@integer/flip_time"
|
||||
android:autoStart="true"
|
||||
>
|
||||
android:layout_gravity="center_vertical"
|
||||
android:autoStart="true">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:textSize="16sp"
|
||||
android:singleLine="true"
|
||||
android:id="@+id/changePercent"
|
||||
tools:text="GOOG"
|
||||
android:gravity="end" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:textSize="16sp"
|
||||
android:singleLine="true"
|
||||
android:id="@+id/changeValue"
|
||||
tools:text="GOOG"
|
||||
android:gravity="end" />
|
||||
|
||||
</ViewFlipper>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:id="@+id/dragHandler">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@android:drawable/arrow_up_float" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@android:drawable/arrow_down_float" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user