Make fancymenu a ListPopupWindow

This commit is contained in:
Adrian Ulrich 2018-11-22 21:06:58 +01:00
parent d85120e9a1
commit 715b80044f
7 changed files with 211 additions and 51 deletions

View File

@ -457,13 +457,16 @@ public class FileSystemAdapter
* Context menu of a row: this was dispatched by LibraryPagerAdapter * Context menu of a row: this was dispatched by LibraryPagerAdapter
* *
* @param intent likely created by createData() * @param intent likely created by createData()
* @param view the parent view
* @param x x-coords of event
* @param y y-coords of event
*/ */
public boolean onCreateFancyMenu(Intent intent) { public boolean onCreateFancyMenu(Intent intent, View view, float x, float y) {
String path = intent.getStringExtra(LibraryAdapter.DATA_FILE); String path = intent.getStringExtra(LibraryAdapter.DATA_FILE);
boolean isParentRow = (path != null && pointsToParentFolder(new File(path))); boolean isParentRow = (path != null && pointsToParentFolder(new File(path)));
if (!isParentRow) if (!isParentRow)
return mActivity.onCreateFancyMenu(intent); return mActivity.onCreateFancyMenu(intent, view, x, y);
// else: no context menu, but consume event. // else: no context menu, but consume event.
return true; return true;
} }

View File

@ -627,13 +627,13 @@ public class LibraryActivity
/** /**
* Creates a context menu for an adapter row. * Creates a context menu for an adapter row.
* *
* @param menu The menu to create.
* @param rowData Data for the adapter row. * @param rowData Data for the adapter row.
* @param view the view which was clicked.
* @param x x-coords of event
* @param y y-coords of event
*/ */
public boolean onCreateFancyMenu(Intent rowData) { public boolean onCreateFancyMenu(Intent rowData, View view, float x, float y) {
FancyMenu fm = new FancyMenu(this, this); FancyMenu fm = new FancyMenu(this, this);
fm.show(getFragmentManager(), "LibraryActivityContext");
// Add to playlist is always available. // Add to playlist is always available.
fm.addSpacer(20); fm.addSpacer(20);
fm.add(CTX_MENU_ADD_TO_PLAYLIST, 20, R.drawable.menu_add_to_playlist, R.string.add_to_playlist).setIntent(rowData); fm.add(CTX_MENU_ADD_TO_PLAYLIST, 20, R.drawable.menu_add_to_playlist, R.string.add_to_playlist).setIntent(rowData);
@ -682,6 +682,7 @@ public class LibraryActivity
fm.addSpacer(90); fm.addSpacer(90);
fm.add(CTX_MENU_DELETE, 90, R.drawable.menu_delete, R.string.delete).setIntent(rowData); fm.add(CTX_MENU_DELETE, 90, R.drawable.menu_delete, R.string.delete).setIntent(rowData);
} }
fm.show(view, x, y);
return true; return true;
} }

View File

@ -23,6 +23,8 @@
package ch.blinkenlights.android.vanilla; package ch.blinkenlights.android.vanilla;
import ch.blinkenlights.android.vanilla.ext.CoordClickListener;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
@ -50,7 +52,7 @@ public class LibraryPagerAdapter
extends PagerAdapter extends PagerAdapter
implements Handler.Callback implements Handler.Callback
, ViewPager.OnPageChangeListener , ViewPager.OnPageChangeListener
, AdapterView.OnItemLongClickListener , CoordClickListener.Callback
, AdapterView.OnItemClickListener , AdapterView.OnItemClickListener
{ {
/** /**
@ -323,11 +325,12 @@ public class LibraryPagerAdapter
throw new IllegalArgumentException("Invalid media type: " + type); throw new IllegalArgumentException("Invalid media type: " + type);
} }
CoordClickListener ccl = new CoordClickListener(this);
view = (ListView)inflater.inflate(R.layout.listview, null); view = (ListView)inflater.inflate(R.layout.listview, null);
view.setOnItemLongClickListener(this); ccl.registerForOnItemLongClickListener(view);
view.setOnItemClickListener(this); view.setOnItemClickListener(this);
view.setTag(type); view.setTag(type);
if (header != null) { if (header != null) {
header.setText(mHeaderText); header.setText(mHeaderText);
header.setTag(new ViewHolder()); // behave like a normal library row header.setTag(new ViewHolder()); // behave like a normal library row
@ -859,17 +862,19 @@ public class LibraryPagerAdapter
* @param view the long clicked view * @param view the long clicked view
* @param position row position * @param position row position
* @param id id of the long clicked row * @param id id of the long clicked row
* @param x x-coords of event
* @param y y-coords of event
* *
* @return true if the event was consumed * @return true if the event was consumed
*/ */
public boolean onItemLongClick (AdapterView<?> parent, View view, int position, long id) { public boolean onItemLongClickWithCoords (AdapterView<?> parent, View view, int position, long id, float x, float y) {
Intent intent = id == LibraryAdapter.HEADER_ID ? createHeaderIntent(view) : mCurrentAdapter.createData(view); Intent intent = id == LibraryAdapter.HEADER_ID ? createHeaderIntent(view) : mCurrentAdapter.createData(view);
int type = (Integer)((View)view.getParent()).getTag(); int type = (Integer)((View)view.getParent()).getTag();
if (type == MediaUtils.TYPE_FILE) { if (type == MediaUtils.TYPE_FILE) {
return mFilesAdapter.onCreateFancyMenu(intent); return mFilesAdapter.onCreateFancyMenu(intent, view, x, y);
} }
return mActivity.onCreateFancyMenu(intent); return mActivity.onCreateFancyMenu(intent, view, x, y);
} }
@Override @Override

View File

@ -207,7 +207,6 @@ public class PlaylistActivity extends Activity
intent.putExtra("audioId", holder.id); intent.putExtra("audioId", holder.id);
FancyMenu fm = new FancyMenu(this, this); FancyMenu fm = new FancyMenu(this, this);
fm.show(getFragmentManager(), "PlaylistActivityContext");
fm.setHeaderTitle(holder.title); fm.setHeaderTitle(holder.title);
fm.add(MENU_PLAY, 0, R.drawable.menu_play, R.string.play).setIntent(intent); fm.add(MENU_PLAY, 0, R.drawable.menu_play, R.string.play).setIntent(intent);
@ -221,6 +220,7 @@ public class PlaylistActivity extends Activity
fm.addSpacer(0); fm.addSpacer(0);
fm.add(MENU_SHOW_DETAILS, 0, R.drawable.menu_details, R.string.details).setIntent(intent); fm.add(MENU_SHOW_DETAILS, 0, R.drawable.menu_details, R.string.details).setIntent(intent);
fm.add(MENU_REMOVE, 0, R.drawable.menu_remove, R.string.remove).setIntent(intent); fm.add(MENU_REMOVE, 0, R.drawable.menu_remove, R.string.remove).setIntent(intent);
fm.show(view);
return true; return true;
} }

View File

@ -108,7 +108,6 @@ public class ShowQueueFragment extends Fragment
intent.putExtra("position", pos); intent.putExtra("position", pos);
FancyMenu fm = new FancyMenu(getActivity(), this); FancyMenu fm = new FancyMenu(getActivity(), this);
fm.show(getFragmentManager(), "ShowQueueFragmentContext");
fm.setHeaderTitle(song.title); fm.setHeaderTitle(song.title);
fm.add(CTX_MENU_PLAY, 0, R.drawable.menu_play, R.string.play).setIntent(intent); fm.add(CTX_MENU_PLAY, 0, R.drawable.menu_play, R.string.play).setIntent(intent);
@ -122,6 +121,7 @@ public class ShowQueueFragment extends Fragment
fm.addSpacer(0); fm.addSpacer(0);
fm.add(CTX_MENU_SHOW_DETAILS, 0, R.drawable.menu_details, R.string.details).setIntent(intent); fm.add(CTX_MENU_SHOW_DETAILS, 0, R.drawable.menu_details, R.string.details).setIntent(intent);
fm.add(CTX_MENU_REMOVE, 90, R.drawable.menu_remove, R.string.remove).setIntent(intent); fm.add(CTX_MENU_REMOVE, 90, R.drawable.menu_remove, R.string.remove).setIntent(intent);
fm.show(view);
return true; return true;
} }

View File

@ -0,0 +1,92 @@
/*
* Copyright (C) 2018 Adrian Ulrich <adrian@blinkenlights.ch>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package ch.blinkenlights.android.vanilla.ext;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.AdapterView;
import android.widget.ListView;
public class CoordClickListener
implements
AdapterView.OnItemLongClickListener
, View.OnTouchListener {
/**
* Interface to implement by the callback
*/
public interface Callback {
boolean onItemLongClickWithCoords(AdapterView<?> parent, View view, int position, long id, float x, float y);
}
/**
* The registered callback consumer
*/
private Callback mCallback;
/**
* Last X position seen.
*/
private float mPosX;
/**
* Last Y position seen.
*/
private float mPosY;
/**
* Create a new CoordClickListener instance
*
* @param cb the callback consumer.
*/
public CoordClickListener(Callback cb) {
mCallback = cb;
}
/**
* Register a view for long click observation.
*
* @param view the view to listen for long clicks
*/
public void registerForOnItemLongClickListener(ListView view) {
view.setOnItemLongClickListener(this);
view.setOnTouchListener(this);
}
/**
* Implementation of onItemLongClickListener interface
*/
@Override
public boolean onItemLongClick (AdapterView<?>parent, View view, int position, long id) {
return mCallback.onItemLongClickWithCoords(parent, view, position, id, mPosX, mPosY);
}
/**
* Implementation of OnTouchListener interface
*/
@Override
public boolean onTouch(View view, MotionEvent ev) {
mPosX = ev.getX();
mPosY = ev.getY();
// Not handled: we just observe.
return false;
}
}

View File

@ -25,26 +25,23 @@ package ch.blinkenlights.android.vanilla.ui;
import ch.blinkenlights.android.vanilla.R; import ch.blinkenlights.android.vanilla.R;
import ch.blinkenlights.android.vanilla.ThemeHelper; import ch.blinkenlights.android.vanilla.ThemeHelper;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ListPopupWindow;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ImageView; import android.widget.ImageView;
import android.util.DisplayMetrics;
import java.util.ArrayList; import java.util.ArrayList;
import android.util.Log;
public class FancyMenu extends DialogFragment public class FancyMenu {
implements DialogInterface.OnClickListener {
/** /**
* Title to use for this FancyMenu * Title to use for this FancyMenu
*/ */
@ -61,10 +58,6 @@ public class FancyMenu extends DialogFragment
* List of all items and possible children * List of all items and possible children
*/ */
private ArrayList<ArrayList<FancyMenuItem>> mItems; private ArrayList<ArrayList<FancyMenuItem>> mItems;
/**
* The built adapter used by the visible dialog
*/
private FancyMenu.Adapter mAdapter;
/** /**
* The callback interface to implement * The callback interface to implement
*/ */
@ -108,10 +101,11 @@ public class FancyMenu extends DialogFragment
/** /**
* Adds a new item to the menu * Adds a new item to the menu
* *
* @param id the id which identifies this object. * @param id the id which identifies this object
* @param order how to sort this item * @param order how to sort this item
* @param drawable icon drawable to use * @param drawable icon drawable to use
* @param text string label * @param text string label
* @return a new FancyMenuItem
*/ */
public FancyMenuItem add(int id, int order, int drawable, CharSequence text) { public FancyMenuItem add(int id, int order, int drawable, CharSequence text) {
return addInternal(id, order, drawable, text, false); return addInternal(id, order, drawable, text, false);
@ -121,11 +115,22 @@ public class FancyMenu extends DialogFragment
* Adds a new spacer item * Adds a new spacer item
* *
* @param order how to sort this item * @param order how to sort this item
* @return a new FancyMenuItem
*/ */
public FancyMenuItem addSpacer(int order) { public FancyMenuItem addSpacer(int order) {
return addInternal(0, order, 0, null, true); return addInternal(0, order, 0, null, true);
} }
/**
* Internal add implementation
*
* @param id the id which identifies the object
* @param order how to sort this item
* @param icon the icon resource to use
* @param text the text label to display
* @param spacer whether or not this is a spacer
* @return a new FancyMenuItem
*/
private FancyMenuItem addInternal(int id, int order, int icon, CharSequence text, boolean spacer) { private FancyMenuItem addInternal(int id, int order, int icon, CharSequence text, boolean spacer) {
FancyMenuItem item = new FancyMenuItem(mContext, id) FancyMenuItem item = new FancyMenuItem(mContext, id)
.setIcon(icon) .setIcon(icon)
@ -140,45 +145,99 @@ public class FancyMenu extends DialogFragment
} }
/** /**
* Called when this dialog is about to be shown. * Creates an Adapter from a nested ArrayList
*
* @param items the nested list containing the items
* @return the new adapter
*/ */
@Override private Adapter assembleAdapter(ArrayList<ArrayList<FancyMenuItem>> items) {
public Dialog onCreateDialog(Bundle savedInstanceState) { final Adapter adapter = new Adapter(mContext, 0);
super.onCreate(savedInstanceState);
// spacers look awful on holo themes // spacers look awful on holo themes
final boolean usesSpacers = !ThemeHelper.usesHoloTheme(); final boolean usesSpacers = !ThemeHelper.usesHoloTheme();
for (ArrayList<FancyMenuItem> sub : items) {
// The adaper will back this list
mAdapter = new FancyMenu.Adapter(mContext, 0);
for (ArrayList<FancyMenuItem> sub : mItems) {
for (FancyMenuItem item : sub ) { for (FancyMenuItem item : sub ) {
if (usesSpacers || !item.isSpacer()) { if (usesSpacers || !item.isSpacer()) {
mAdapter.add(item); adapter.add(item);
} }
} }
} }
return adapter;
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(mTitle)
.setAdapter(mAdapter, this);
return builder.create();
} }
/** /**
* Callback for click events on the displayed dialog * Measures the predicted total height and max width of given adapter.
*
* @param adapter the adapter to measure
* @param result int array with two elements. 0 is the max height, 1 the longest width.
*/ */
@Override private void measureAdapter(Adapter adapter, int[] result) {
public void onClick(DialogInterface dialog, int which) { result[0] = 0;
dialog.dismiss(); result[1] = 0;
for (int i = 0; i < adapter.getCount(); i++) {
FancyMenuItem item = mAdapter.getItem(which); View view = adapter.getView(i, null, null);
if (!item.isSpacer()) { view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
mCallback.onFancyItemSelected(item); View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
result[0] += view.getMeasuredHeight();
if (result[1] < view.getMeasuredWidth()) {
result[1] = view.getMeasuredWidth();
}
} }
} }
/**
* Calculate the height that should be used for the menu
*
* @param the estimated height
* @return the height to use instead of the input
*/
private int getMenuHeight(int suggested) {
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
int maxHeight = (int)((float)metrics.heightPixels * 0.35);
return (suggested > maxHeight ? maxHeight : ListPopupWindow.WRAP_CONTENT);
}
public void show(View parent) {
show(parent, Float.NaN, Float.NaN);
}
/**
* Show the assembled fancy menu
*
* @param the parent view to use as anchor
* @param x x-coord position hint
* @param y y-coord position hint
*/
public void show(View parent, float x, float y) {
final ListPopupWindow pw = new ListPopupWindow(mContext);
final Adapter adapter = assembleAdapter(mItems);
AdapterView.OnItemClickListener listener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
FancyMenuItem item = adapter.getItem(pos);
if (!item.isSpacer()) {
mCallback.onFancyItemSelected(item);
}
pw.dismiss();
}
};
int result[] = new int[2];
measureAdapter(adapter, result);
pw.setHeight(getMenuHeight(result[0]));
pw.setWidth(result[1]);
pw.setAdapter(adapter);
pw.setOnItemClickListener(listener);
pw.setModal(true);
pw.setAnchorView(parent);
if (!Float.isNaN(x) && !Float.isNaN(y)) {
parent.getLocationInWindow(result);
pw.setHorizontalOffset((int)x - result[0]);
}
pw.show();
}
/** /**
* Adapter class backing all menu entries * Adapter class backing all menu entries