I have made an Activity called Accounts and I want to add a horizontal ContextMenu. This may look like the cut, copy and paste options. Is there any way to add this horizontal custom menu onLongClick on the list items?
Here's what I've got so far.
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
LayoutInflater inflater = getLayoutInflater().from(this);
View view = inflater.inflate(R.layout.custom_listview, null, false);
menu.setHeaderView(view);
menu.add("Delete");
menu.add("Edit");
}
#Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
int position = info.position;
View v = listView.getChildAt(position);
TextView typeTv = v.findViewById(R.id.custom_listview_type);
TextView userTv = v.findViewById(R.id.custom_listview_user);
TextView passTv = v.findViewById(R.id.custom_listview_password);
if (item.getTitle().equals("Delete")) {
db.execSQL("delete from user_added_accounts where accountType = '" + typeTv.getText().toString() + "' and username = '" + userTv.getText().toString() + "';");
recreate();
}
if (item.getTitle().equals("Edit")) {
update(typeTv.getText().toString(), userTv.getText().toString(), passTv.getText().toString());
}
return true;
}
And the current UI looks like this.
Here is something like I want,
Simply you can achieve by QuickAction library.
https://github.com/piruin/quickaction
https://github.com/lorensiuswlt/NewQuickAction
Hope this will help you!!
I think the thing you need is the PopupWindow. Its easier to implement and has its custom layout setting option. The PopupWindow can be set in custom position as you wish and the idea of implementing a sample copy/paste UI that you are thinking of, can be served with the implementation of PopupWindow as well.
I found this answer very informative if you want to implement your situation with PopupWindow instead of implementing it with context menu.
In the above answer that I mentioned and provided a like to, has a PopupWindow which has a TextView only. You might implement any custom/complex UI instead of having a simple TextView like its shown there.
I hope that helps.
Update
As asked in the comment that getting locations of the position of PopupWindow can be set dynamically as well. I am referring to another link, so that you can check the implementation from there as well.
Here's the implementation of using PopupWindow in a list.
So , I wrote below code few year back. You need to make two class first is PopUp and second is TringleView.
PopUp :- Make dialog box and open into near your view(where you want
to open dialog). You can change popup bg color.
TringleView :- Make tringle view or you can say pointed arrow.You can change pointed arrow bg color.
View contentView = ((FragmentActivity)v.getContext()).getLayoutInflater().inflate(R.layout.edit_delete_layout,getAttachedRecyclerView(),false);
// this view denote where you click or you want to open dialog near
PopUp.showPopupOnView(((FragmentActivity) v.getContext()).getSupportFragmentManager(),contentView,view,false);
PopUp.class
public class PopUp extends DialogFragment {
protected int targetX;
protected int targetY;
protected int targetWidth;
protected int targetHeight;
protected Bitmap targetViewImage;
protected View contentView;
private SmartWorksPopUpViewHolder fragmentViewHolder;
private static int bgDrawable = R.drawable.round_corner_white_bg;
protected static int ONE_DIP;
private static int arrowBgColor = R.color.border_color;
private static int arrowWidthMultiple = 25;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (ONE_DIP == 0) {
ONE_DIP = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 1, getResources()
.getDisplayMetrics());
}
setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme_Translucent);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
AbsoluteLayout parent = new AbsoluteLayout(getActivity());
parent.setId(R.id.parentLayout);
return parent;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
this.fragmentViewHolder = createViewHolder(view);
bindView(fragmentViewHolder);
}
protected SmartWorksPopUpViewHolder createViewHolder(
View fragmentView) {
return new SmartWorksPopUpViewHolder(fragmentView, contentView);
}
private void bindView(SmartWorksPopUpViewHolder vh) {
if (fragmentViewHolder != null) {
setupTargetDummyView(vh);
boolean showOnTop = shouldShowOnTop();
setupArrow(vh, showOnTop);
setupContent(vh, showOnTop);
}
}
protected void setupContent(SmartWorksPopUpViewHolder vh, boolean showOnTop) {
final int y;
AbsoluteLayout.LayoutParams arrowParams = (android.widget.AbsoluteLayout.LayoutParams) vh.arrow
.getLayoutParams();
int measureHeight = View.MeasureSpec.makeMeasureSpec(
ViewGroup.LayoutParams.WRAP_CONTENT, View.MeasureSpec.UNSPECIFIED);
int measureWidth = View.MeasureSpec.makeMeasureSpec(
getActivity().getWindow().getDecorView().getWidth(), View.MeasureSpec.EXACTLY);
vh.popupView.measure(measureWidth, measureHeight);
if (showOnTop) {
y = this.targetY - vh.popupView.getMeasuredHeight() + ONE_DIP;
} else {
y = arrowParams.y + arrowParams.height - ONE_DIP * 2;
}
updateAbsoluteLayoutParams(
getActivity().getResources().getDimensionPixelOffset(R.dimen.sixty_dp),
y,
getActivity().getWindow().getDecorView().getWidth() -
getActivity().getResources().getDimensionPixelOffset(R.dimen.seventy_dp),
ViewGroup.LayoutParams.WRAP_CONTENT, vh.popupView);
vh.parent.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
exit();
}
});
}
private void setupArrow(SmartWorksPopUpViewHolder vh, boolean showOnTop) {
final int arrowHeight = 15 * ONE_DIP;
final int arrowWidth = arrowWidthMultiple * ONE_DIP;
vh.arrow.setDirectionAndColor(showOnTop ? "down" : "top", vh.popupView.getContext().getResources().getColor(arrowBgColor));
final int x = (int) (targetX + targetWidth / 3 - arrowWidth / 2);
final int y = targetY + (showOnTop ? -arrowHeight : targetHeight);
updateAbsoluteLayoutParams(x, y, arrowWidth, arrowHeight, vh.arrow);
}
private void setupTargetDummyView(SmartWorksPopUpViewHolder vh) {
vh.targetViewDummy.setImageBitmap(targetViewImage);
updateAbsoluteLayoutParams(targetX, targetY, targetWidth, targetHeight, vh.targetViewDummy);
}
protected void updateAbsoluteLayoutParams(int x, int y, int width, int height, View view) {
AbsoluteLayout.LayoutParams layoutParams =
(android.widget.AbsoluteLayout.LayoutParams) view.getLayoutParams();
layoutParams.x = x;
layoutParams.y = y;
layoutParams.height = height;
layoutParams.width = width;
view.setLayoutParams(layoutParams);
}
private boolean shouldShowOnTop() {
int windowHeight = getActivity().getWindow().getDecorView().getHeight();
int windowMid = windowHeight / 4;
return targetY > windowMid;
}
#Override
public void onDestroyView() {
this.fragmentViewHolder = null;
super.onDestroyView();
}
protected static class SmartWorksPopUpViewHolder {
protected AbsoluteLayout parent;
protected View popupView;
protected TringleView arrow;
protected AppCompatImageView targetViewDummy;
protected SmartWorksPopUpViewHolder(View fragmentView, View content) {
this.parent = (AbsoluteLayout) fragmentView;
final Context mContext = fragmentView.getContext();
this.popupView = content;
this.arrow = new TringleView(mContext);
this.targetViewDummy = new SmartWorksAppCompactImageView(mContext);
this.parent.addView(popupView);
this.parent.addView(arrow);
this.parent.addView(targetViewDummy);
this.parent.setBackgroundColor(0x00000000);
content.setBackgroundResource(bgDrawable);
}
}
public static PopUp showPopupOnView(FragmentManager fm, View contentView, View targetView, boolean showTargetView) {
int[] location = new int[2];
targetView.getLocationInWindow(location);
PopUp fragment = new PopUp();
fragment.targetX = location[0];
fragment.targetY = (int) (location[1] - TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 25,
targetView.getResources().getDisplayMetrics()));
fragment.targetWidth = targetView.getMeasuredWidth();
fragment.targetHeight = targetView.getMeasuredHeight();
fragment.contentView = contentView;
fragment.show(fm, "offer");
return fragment;
}
public void exit() {
dismiss();
}
public static void setArrowBackgroundColor(int color) {
arrowBgColor = color;
}
public static void setArrowWidthMultiple(int arrowWidth) {
arrowWidthMultiple = arrowWidth;
}
}
TringleView.class
public class TringleView extends View {
private String direction;
private int color;
public TringleView(Context context) {
super(context);
setDirectionAndColor("right", Color.RED);
}
public TringleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setDirectionAndColor(attrs.getAttributeValue(null, "direction"), Color.RED);
}
public TringleView(Context context, AttributeSet attrs) {
super(context, attrs);
setDirectionAndColor(attrs.getAttributeValue(null, "direction"), Color.RED);
}
public void setDirectionAndColor(String direction, int color) {
if (direction != null && !direction.equals(this.direction) || this.color != color) {
createTriangleDrawable(direction, color);
}
}
private void createTriangleDrawable(String string, int color) {
int width = MeasureSpec.makeMeasureSpec(30, MeasureSpec.UNSPECIFIED);
int height = MeasureSpec.makeMeasureSpec(20, MeasureSpec.UNSPECIFIED);
Path path = new Path();
if (string == null) {
string = "right";
}
if (string.equals("top")) {
path.moveTo(0, height);
path.lineTo(width / 2, 0);
path.lineTo(width, height);
} else if (string.equals("left")) {
path.moveTo(width, 0);
path.lineTo(0, height / 2);
path.lineTo(width, height);
} else if (string.equals("right")) {
path.moveTo(0, 0);
path.lineTo(width, height / 2);
path.lineTo(0, height);
} else if (string.equals("down")) {
path.moveTo(0, 0);
path.lineTo(width / 2, height);
path.lineTo(width, 0);
}
path.close();
ShapeDrawable shapeDrawable = new ShapeDrawable(new PathShape(path, width, height));
shapeDrawable.getPaint().setColor(color);
setBackground(shapeDrawable);
this.color = color;
this.direction = string;
}
}
edit_delete_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
app:cardCornerRadius="#dimen/five_dp"
android:layout_margin="#dimen/ten_dp"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="5"
android:gravity="center"
android:orientation="horizontal"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<sis.com.smartworks.widget.SmartWorksTextView
android:id="#+id/share"
android:textSize="#dimen/smallest_text_size"
android:textColor="#color/black"
android:visibility="visible"
android:gravity="center"
android:paddingRight="#dimen/four_dp"
android:paddingLeft="#dimen/four_dp"
android:paddingTop="#dimen/ten_dp"
android:paddingBottom="#dimen/ten_dp"
android:text="Share"
android:layout_weight="1"
android:textStyle="bold"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<View
android:layout_width="1dp"
android:background="#color/grey_unselect"
android:layout_height="match_parent" />
<sis.com.smartworks.widget.SmartWorksTextView
android:id="#+id/reportSpam"
android:textSize="#dimen/smallest_text_size"
android:textColor="#color/black"
android:visibility="visible"
android:gravity="center"
android:paddingRight="#dimen/four_dp"
android:paddingLeft="#dimen/four_dp"
android:paddingTop="#dimen/ten_dp"
android:paddingBottom="#dimen/ten_dp"
android:text="Spam"
android:layout_weight="1"
android:textStyle="bold"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<View
android:layout_width="1dp"
android:background="#color/grey_unselect"
android:layout_height="match_parent" />
<!--<View-->
<!--android:layout_width="match_parent"-->
<!--android:layout_marginLeft="#dimen/three_dp"-->
<!--android:layout_marginRight="#dimen/three_dp"-->
<!--android:background="#color/white"-->
<!--android:layout_height="#dimen/one_dp" />-->
<sis.com.smartworks.widget.SmartWorksTextView
android:id="#+id/edit"
android:textSize="#dimen/smallest_text_size"
android:textColor="#color/black"
android:gravity="center"
android:paddingRight="#dimen/four_dp"
android:paddingLeft="#dimen/four_dp"
android:paddingTop="#dimen/ten_dp"
android:paddingBottom="#dimen/ten_dp"
app:swFontName="robotoNormal"
android:layout_weight="1"
android:text="#string/edit"
android:textStyle="bold"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<View
android:layout_width="1dp"
android:background="#color/grey_unselect"
android:layout_height="match_parent" />
<!--<View-->
<!--android:layout_width="match_parent"-->
<!--android:layout_marginLeft="#dimen/three_dp"-->
<!--android:layout_marginRight="#dimen/three_dp"-->
<!--android:background="#color/white"-->
<!--android:layout_height="#dimen/one_dp" />-->
<sis.com.smartworks.widget.SmartWorksTextView
android:id="#+id/delete"
android:textSize="#dimen/smallest_text_size"
android:textColor="#color/black"
android:gravity="center"
android:paddingRight="#dimen/four_dp"
android:paddingLeft="#dimen/four_dp"
android:paddingTop="#dimen/ten_dp"
android:paddingBottom="#dimen/ten_dp" android:layout_weight="1"
android:text="#string/delete"
android:textStyle="bold"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<View
android:layout_width="1dp"
android:background="#color/grey_unselect"
android:layout_height="match_parent" />
<sis.com.smartworks.widget.SmartWorksTextView
android:id="#+id/cancel"
android:textSize="#dimen/smallest_text_size"
android:textColor="#color/black"
android:gravity="center"
android:layout_weight="1"
android:visibility="visible"
android:paddingRight="#dimen/four_dp"
android:paddingLeft="#dimen/four_dp"
android:paddingTop="#dimen/ten_dp"
android:paddingBottom="#dimen/ten_dp"
android:textStyle="bold"
android:text="#string/select_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.v7.widget.CardView>
Result
So if you want to make view as horizontal then you need to make horizontal layout according to your requirement. So can do this task to change your edit_delete_layout.xml which your putting into contentView then pass to Popup class method.
Note:- You can customise popup class according to your requirement and I know this code having so many deprecated view so you can update yourself.
To show compact contextual menu you need to create ActionMode for the Menu, let me show you how:
Suppose your action menu XML have delete, copy and forward actions:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/action_copy"
android:icon="#drawable/ic_vector_menu_copy"
android:title="Copy"
app:showAsAction="always" />
<item
android:id="#+id/action_delete"
android:icon="#drawable/ic_vector_menu_delete"
android:title="Delete"
app:showAsAction="always" />
<item
android:id="#+id/action_forward"
android:icon="#drawable/ic_vector_menu_forward"
android:title="Forward"
app:showAsAction="always" />
</menu>
Create your action menu in your Activity
//Global variable in Activity/Fragment to manage close the menu
private ActionMode mActionMode;
//Action mode callbacks
//Contextual Action bar - for showing delete/copy/... on action bar
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
// Called when the action mode is created; startActionMode() was called
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate a menu resource providing context menu items
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.menu_contextual_action, menu);
return true;
}
// Called each time the action mode is shown.
// Always called after onCreateActionMode, but
// may be called multiple times if the mode is invalidated.
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false; // Return false if nothing is done
}
// Called when the user selects a contextual menu item
#Override
public boolean onActionItemClicked(final ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.action_delete:
//Do the delete action
//mAdapter.resetSelection();
mode.finish(); // Action picked, so close the TAB
//showToast "Deleted successfully"
return true;
case R.id.action_copy:
//mAdapter.resetSelection();
MyClipboardManager.copyToClipboard(ChatDetailActivity.this, mAdapter.getSelectedMessageText());
mode.finish(); // Action picked, so close the TAB
//showToast "Text copied to clipboard"
return true;
default:
return false;
}
}
// Called when the user exits the action mode
#Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
//mAdapter.resetSelection();
}
};
#Override
public void onBackPressed() {
//Closing menu first if it's visible rather than doing the back press action
if (mActionMode != null && mActionMode.getMenu().hasVisibleItems()) {
mActionMode.finish();
return;
}
super.onBackPressed();
}
#Override
public void onDestroy() {
//Closing menu
if (mActionMode != null) {
mActionMode.finish();
}
super.onDestroy();
}
*Set the callback to the global action mode variable
mActionMode = startSupportActionMode(mActionModeCallback);
*Set title to the menu
mActionMode.setTitle("Menu title");
*Invalidate the menu after settings value
mActionMode.invalidate();
Style to manage compact contextual menu
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
<item name="android:windowDisablePreview">true</item>
<!--CONTEXTUAL action MODE-->
<item name="android:windowContentOverlay">#null</item>
<!--ActionMode background color-->
<!-- <item name="android:actionModeBackground">#color/colorPrimary</item>-->
<!--To Overlay existing toolbar, NOTE We are not using android: to let it work everywhere-->
<item name="windowActionModeOverlay">true</item>
<item name="actionModeStyle">#style/AppActionModeStyle</item>
<item name="android:actionModeCloseDrawable">#drawable/ic_arrow_back_24dp</item>
</style>
<style name="AppActionModeStyle" parent="#style/Widget.AppCompat.ActionMode">
<!--ActionMode background color-->
<item name="background">#color/colorPrimary</item>
<!--ActionMode text title color-->
<item name="titleTextStyle">#style/ActionModeTitleTextStyle</item>
</style>
<style name="ActionModeTitleTextStyle" parent="#style/TextAppearance.AppCompat.Widget.ActionMode.Title">
<item name="android:textColor">#android:color/white</item>
</style>
Related
I really like the new PopupMenu we got in 3.0, but I just can't display any icons next to the menu items in it. I'm inflating the menu from the .xml below:
<item android:id="#+id/menu_delete_product"
android:icon="#drawable/sym_action_add"
android:title="delete"
android:showAsAction="ifRoom|withText" />
<item android:id="#+id/menu_modify_product"
android:icon="#drawable/sym_action_add"
android:title="modify"
android:showAsAction="ifRoom|withText" />
<item android:id="#+id/menu_product_details"
android:icon="#drawable/sym_action_add"
android:title="details"
android:showAsAction="ifRoom|withText" />
With this code:
image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
PopupMenu pop = new PopupMenu(getActivity(), v);
pop.getMenuInflater().inflate(R.menu.shelves_details_menu, pop.getMenu());
pop.show();
}
});
I can't get the icons to show up, am I missing something?
Contribution to the solution provided by Gaelan Bolger.
Use this code if you get a "IllegalAccessException: access to field not allowed".
PopupMenu popup = new PopupMenu(mContext, view);
try {
Field[] fields = popup.getClass().getDeclaredFields();
for (Field field : fields) {
if ("mPopup".equals(field.getName())) {
field.setAccessible(true);
Object menuPopupHelper = field.get(popup);
Class<?> classPopupHelper = Class.forName(menuPopupHelper
.getClass().getName());
Method setForceIcons = classPopupHelper.getMethod(
"setForceShowIcon", boolean.class);
setForceIcons.invoke(menuPopupHelper, true);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
prepareMenu(popup.getMenu());
popup.show();
text
If you're willing to be a bit adventurous, look at Google's source code for PopupMenu. Create your own class i.e. MyPopupMenu that is the same as Google's PopupMenu class, but make one slight change.
In PopupMenu's constructor:
public MyPopupMenu(Context context, View anchor) {
// TODO Theme?
mContext = context;
mMenu = new MenuBuilder(context);
mMenu.setCallback(this);
mAnchor = anchor;
mPopup = new MenuPopupHelper(context, mMenu, anchor);
mPopup.setCallback(this);
mPopup.setForceShowIcon(true); //ADD THIS LINE
}
use the method setForceShowIcon to force it to show the icon. You can also just expose a public method to set this flag as well depending on your needs.
I was able to show the icons using reflection. It may not be the most elegant solution but it works.
try {
Class<?> classPopupMenu = Class.forName(popupMenu
.getClass().getName());
Field mPopup = classPopupMenu.getDeclaredField("mPopup");
mPopup.setAccessible(true);
Object menuPopupHelper = mPopup.get(popupMenu);
Class<?> classPopupHelper = Class.forName(menuPopupHelper
.getClass().getName());
Method setForceIcons = classPopupHelper.getMethod(
"setForceShowIcon", boolean.class);
setForceIcons.invoke(menuPopupHelper, true);
} catch (Exception e) {
e.printStackTrace();
}
We can use sub-menu model. So, we don't need to write method for showing popup menu, it will be showing automacally. Have a look:
menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/action_more"
android:icon="#android:drawable/ic_menu_more"
android:orderInCategory="1"
android:showAsAction="always"
android:title="More">
<menu>
<item
android:id="#+id/action_one"
android:icon="#android:drawable/ic_popup_sync"
android:title="Sync"/>
<item
android:id="#+id/action_two"
android:icon="#android:drawable/ic_dialog_info"
android:title="About"/>
</menu>
</item>
</menu>
in MainActivity.java
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
The result is:
before use method popup.show(),make a MenuPopupHelper instance and call method setForceShowIcon(true),like this
try {
Field mFieldPopup=popupMenu.getClass().getDeclaredField("mPopup");
mFieldPopup.setAccessible(true);
MenuPopupHelper mPopup = (MenuPopupHelper) mFieldPopup.get(popupMenu);
mPopup.setForceShowIcon(true);
} catch (Exception e) {
}
The easiest way I found is that to use MenuBuilder and MenuPopupHelper.
MenuBuilder menuBuilder =new MenuBuilder(this);
MenuInflater inflater = new MenuInflater(this);
inflater.inflate(R.menu.menu, menuBuilder);
MenuPopupHelper optionsMenu = new MenuPopupHelper(this, menuBuilder, view);
optionsMenu.setForceShowIcon(true);
// Set Item Click Listener
menuBuilder.setCallback(new MenuBuilder.Callback() {
#Override
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
switch (item.getItemId()) {
case R.id.opt1: // Handle option1 Click
return true;
case R.id.opt2: // Handle option2 Click
return true;
default:
return false;
}
}
#Override
public void onMenuModeChange(MenuBuilder menu) {}
});
// Display the menu
optionsMenu.show();
menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/opt1"
android:icon="#mipmap/ic_launcher"
android:title="option 1" />
<item
android:id="#+id/opt2"
android:icon="#mipmap/ic_launcher"
android:title="option 2" />
</menu>
I found a native solution for this, using MenuPopupHelper.setForceShowIcon(true).
private void createMenu(int menuRes, View anchor, MenuBuilder.Callback callback) {
Context context = anchor.getContext();
NavigationMenu navigationMenu = new NavigationMenu(context);
navigationMenu.setCallback(callback);
SupportMenuInflater supportMenuInflater = new SupportMenuInflater(context);
supportMenuInflater.inflate(menuRes, navigationMenu);
MenuPopupHelper menuPopupHelper = new MenuPopupHelper(context, navigationMenu, anchor);
menuPopupHelper.setForceShowIcon(true);
menuPopupHelper.show();
}
Usage
private void initMenu(View view) {
view.findViewById(R.id.myButton).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
createMenu(R.menu.help_menu, view, new MenuBuilder.Callback() {
#Override
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
switch (item.getItemId()) {
case R.id.id1:
// Do something
break;
case R.id.id2:
// Do something
break;
case R.id.id3:
// Do something
break;
}
return true;
}
#Override
public void onMenuModeChange(MenuBuilder menu) {
}
});
}
});
}
Along the line of using reflection and without the need to use MenuPopupHelper, you can add
if (popup.getMenu() instanceof MenuBuilder) {
//noinspection RestrictedApi
((MenuBuilder) popup.getMenu()).setOptionalIconsVisible(true);
}
prior to inflating the menu
PopupMenu will not display icons. You can use an ActionBar.
http://developer.android.com/guide/topics/ui/actionbar.html
Some of the solutions above will work with the reflection hack,
Just sharing this: I've recently came across the same issues, but I also wanted to create a more customized thing (adding custom view in the menu) so I created the following lib.
https://github.com/shehabic/Droppy
If you're using AndroidX, which changed the visibility of MenuPopupHelper to package-private, you can avoid the cost of reflection by creating a wrapper class with the same package name.
This exposes package-private members to public.
package androidx.appcompat.widget // Create this package in your project's /src/main/java
import android.annotation.SuppressLint
class PopupMenuWrapper(val t: PopupMenu) {
#SuppressLint("RestrictedApi")
fun setForceShowIcon(show: Boolean) { // Public method
t.mPopup.setForceShowIcon(show)
}
}
fun PopupMenu.wrap() = PopupMenuWrapper(this)
Then call the hidden function as you normally would.
val popup = PopupMenu(anchor.context, anchor)
popup.wrap().setForceShowIcon(true)
popup.show()
If you want to prevent using RestrictedApi use this extention function:
fun PopupMenu.forcePopUpMenuToShowIcons() {
try {
val method = menu.javaClass.getDeclaredMethod(
"setOptionalIconsVisible",
Boolean::class.javaPrimitiveType
)
method.isAccessible = true
method.invoke(menu, true)
} catch (e: Exception) {
e.printStackTrace()
}
}
You can use the setForceShowIcon (true)
PopupMenu(context, view).apply {
setForceShowIcon(true)
menuInflater.inflate(R.menu.menu_edit_professional_experience, menu)
setOnMenuItemClickListener { item ->
Toast.makeText(view.context, "YOU clcick", Toast.LENGTH_LONG).show()
true
}
}.show()
Use setForceShowIcon(true)
The PopupMenu cannot be fully customized. Below you find a general solution to make your PopupMenu customizable via a custom layout. Having that, you can experiment a lot more with different layouts. Cheers.
1 - The Custom PopupMenu class:
public class PopupMenuCustomLayout {
private PopupMenuCustomOnClickListener onClickListener;
private Context context;
private PopupWindow popupWindow;
private int rLayoutId;
private View popupView;
public PopupMenuCustomLayout(Context context, int rLayoutId, PopupMenuCustomOnClickListener onClickListener) {
this.context = context;
this.onClickListener = onClickListener;
this.rLayoutId = rLayoutId;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
popupView = inflater.inflate(rLayoutId, null);
int width = LinearLayout.LayoutParams.WRAP_CONTENT;
int height = LinearLayout.LayoutParams.WRAP_CONTENT;
boolean focusable = true;
popupWindow = new PopupWindow(popupView, width, height, focusable);
popupWindow.setElevation(10);
LinearLayout linearLayout = (LinearLayout) popupView;
for (int i = 0; i < linearLayout.getChildCount(); i++) {
View v = linearLayout.getChildAt(i);
v.setOnClickListener( v1 -> { onClickListener.onClick( v1.getId()); popupWindow.dismiss(); });
}
}
public void setAnimationStyle( int animationStyle) {
popupWindow.setAnimationStyle(animationStyle);
}
public void show() {
popupWindow.showAtLocation( popupView, Gravity.CENTER, 0, 0);
}
public void show( View anchorView, int gravity, int offsetX, int offsetY) {
popupWindow.showAsDropDown( anchorView, 0, -2 * (anchorView.getHeight()));
}
public interface PopupMenuCustomOnClickListener {
public void onClick(int menuItemId);
}
}
2 - Your custom layout, e.g. linearlayout with horizontal layout. In this case I use a simple LinearLayout with TextView items. You can use Buttons, etc.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:orientation="horizontal">
<TextView
android:id="#+id/popup_menu_custom_item_a"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="A"
android:textAppearance="?android:textAppearanceMedium" />
<TextView
android:id="#+id/popup_menu_custom_item_b"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="B"
android:textAppearance="?android:textAppearanceMedium" />
// ...
</LinearLayout>
3 - Using the Custom PopupMenu like the normal PopupMenu.
PopupMenuCustomLayout popupMenu = new PopupMenuCustomLayout(
MainActivity.mainActivity, R.layout.popup_menu_custom_layout,
new PopupMenuCustomLayout.PopupMenuCustomOnClickListener() {
#Override
public void onClick(int itemId) {
// log statement: "Clicked on: " + itemId
switch (itemId) {
case R.id.popup_menu_custom_item_a:
// log statement: "Item A was clicked!"
break;
}
}
});
// Method 1: popupMenu.show();
// Method 2: via an anchor view:
popupMenu.show( anchorView, Gravity.CENTER, 0, 0);
I currently have a simple App that displays news articles from a url in a gridView. I have added a search icon to my main menu xml but I don't know how to get the search function to work. I just need it to display the relevant articles in GridView if the string the user searches is matches the article title. Any help would be appreciated, I don't know how to get started!
I have other files of course, let me know if you need to see them, I am not sure what is helpful.
ActivityMain.java
public class MainActivity extends AppCompatActivity {
private List<NewsRecord> newsListData = new ArrayList<NewsRecord>();
private GridView newsListView;
private NewsListAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GridView newsListView = (GridView) findViewById(R.id.newsFeedList);
adapter = new NewsListAdapter(this, R.layout.adapter_news_list, newsListData, this);
newsListView.setAdapter(adapter);
newsListView.setOnItemClickListener(itemClicked);
nextStart = 0;
updateListData(nextStart, 20);
}
public int nextStart = 0;
public void updateListData(int StartPoint, int count){
String url = "http://www.efstratiou.info/projects/newsfeed/getList.php? start=" + StartPoint + "&count=" + count;
EDANewsApp app = EDANewsApp.getInstance();
JsonArrayRequest jsonRequest = new JsonArrayRequest(url, listener, errorListener);
app.requestQueue.add(jsonRequest);
nextStart +=count;
}
#Override
public boolean onCreateOptionsMenu (Menu menu){
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
main_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/action_about"
android:orderInCategory="10"
app:showAsAction="never"
android:title="About"/>
<item
android:id="#+id/action_settings"
android:orderInCategory="100"
app:showAsAction="never"
android:title="Settings" />
<item android:id="#+id/action_search"
android:title="Search"
android:icon="#android:drawable/ic_menu_search"
app:showAsAction="ifRoom|collapseActionView"
app:actionViewClass="android.support.v7.widget.SearchView"
android:textColor="#FFF"
/>
</menu>
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/newsListItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".MainActivity">
<GridView
android:id="#+id/newsFeedList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:verticalSpacing="0dp"
android:horizontalSpacing="0dp"
android:stretchMode="columnWidth"
android:numColumns="2"/>
</FrameLayout>
ok I will assume that following is your NewsRecord.java
public class NewsRecord {
private String articleTitle;
public String getArticleTitle() {
return articleTitle;
}
public void setArticleTitle(String articleTitle) {
this.articleTitle = articleTitle;
}
}
this is the code you will be using to search
/**
* #param list - this is the Input list in which you want to look up the search text
* #param string - this is the string which would be looked up in the list
* #return this method returns list of NewsRecord of NewsRecord which contains search string in thier article
* */
private static ArrayList<NewsRecord> searchInList(ArrayList<NewsRecord> list, String string) {
// TODO Auto-generated method stub
ArrayList<NewsRecord> temp = new ArrayList<NewsRecord>();
for (NewsRecord item : list) {
if (item.getArticleTitle().contains(string)) {
temp.add(item);
}
}
if (!temp.isEmpty()) {
return temp;
}
return null;
}
following is the code how to utilize this method
lets say you have a editText from which you would be searching..
String searchArticle = editText.getText().toString();
ArrayList<NewsRecord> templist = searchInList(list, searchArticle);
if (!templist .isEmpty()) {
adapter = new NewsListAdapter(this, R.layout.adapter_news_list, templist , this);
newsListView.setAdapter(adapter);
}
I have one ImageView with src, and I want it to change its src onClick. That's easy, but I want to change it's src back to normal when user clicks the imageview again. How can I make it in java?
EDIT:
I already tried this:
public void act1 (View view) {
ImageView ic1 = (ImageView) findViewById(R.id.id1);
Drawable oldBg = ic1.getBackground();
String oldBgStr = ic1.getBackground().toString();
Drawable ic1light = this.getResources().getDrawable(R.drawable.ic1);
Drawable ic1dark = this.getResources().getDrawable(R.drawable.ic1dark);
ic1.setTag(R.drawable.ic1);
if (oldBg == ic1light){
ic1.setBackground(ic1dark);
}
if (oldBg == ic1dark) {
ic1.setBackground(ic1light);
}
ic1.setImageResource(R.drawable.ic1dark);
}
Here is XML of ImageView and Layout it's in:
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:background="#color/red"
>
<ImageView
android:id="#+id/id1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="act1"
android:background="#drawable/ic1"
android:adjustViewBounds="true"/>
Simply set a boolean and toggle it whenever the user clicks on the image. Check the boolean each time and display the appropriate image.
private boolean mClicked = false;
public void act1 (View view) {
if(mClicked) {
ic1.setBackground(ic1dark);
}
else {
ic1.setBackground(ic1light);
}
mClicked = !mClicked;
}
Use a boolean to switch between states of current image set like so:
private boolean currentState = false;
public void act1 (View view) {
ImageView ic1 = (ImageView) findViewById(R.id.id1);
// set current src
Drawable oldBg = ic1.getBackground();
String oldBgStr = ic1.getBackground().toString();
Drawable ic1light = this.getResources().getDrawable(R.drawable.ic1);
Drawable ic1dark = this.getResources().getDrawable(R.drawable.ic1dark);
ic1.setTag(R.drawable.ic1);
// when this is called from click event of anything else
if(currentState){
ic1.setBackground(ic1light);
currentState = true;
return;
}
if(!currentState){
ic1.setBackground(ic1dark);
currentState = false;
return;
}
}
I'm developing an Android app using Xamarin studio, but this is not important.
I use the pageViewer to upload the fragment (12 more or less). To the one display fragment i use a webView to display a local html page and with a swipe to the left, the webview content change to the next one.
So, at the fifth frgament memory problems started even if i used different tasks in my code.
My question is: Is there a way to 'detach' the fragment when i'm not displaying them? Can they not remain into my memory?
Thanks for all
This is my code, N.B: Java answers are accepeted as well
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
SetContentView (Resource.Layout.BookView);
_loader = ProgressDialog.Show (this, "Loading...", "Please wait...", true);
//return chapters count
chapters = 12 //example
//var dp = (int)Resources.DisplayMetrics.Density;
_layout = FindViewById<DrawerLayout> (Resource.Id.drawer_layout);
_view = FindViewById<ViewPager>(Resource.Id.bView);
_view.SetBackgroundColor (Color.White);
_currentAdapter = new AwesomeFragmentAdapter (SupportFragmentManager, path, name, chapters, this, _view);
_view.Adapter = _currentAdapter;
_view.OffscreenPageLimit = chapters;
List<int> positions = new List<int> ();
_view.PageSelected += (object sender, ViewPager.PageSelectedEventArgs e) => {
//get details
var page_load = new Task (() => {
//return an object with the chapter details
_chap = object;
});
page_load.Start();
//find the webview
_web = (WebView)_view.FindViewWithTag(300 + e.Position);
WebSettings setting = _web.Settings;
setting.CacheMode = CacheModes.Default;
setting.JavaScriptEnabled = true;
setting.BuiltInZoomControls = true;
setting.DisplayZoomControls = false;
setting.PluginsEnabled = true;
setting.SetPluginState(WebSettings.PluginState.On);
//setting.JavaScriptCanOpenWindowsAutomatically = true;
if (positions.Contains(e.Position)) {
_web.ClearCache(true);
_web.ClearView();
}
//Start when the scroll is finished
_view.PageScrollStateChanged += (object sendero, ViewPager.PageScrollStateChangedEventArgs ex) => {
if (ex.State == 0 ) {
if (positions.Contains(e.Position)) {
//_web.Reload(); --> doesn't work
//Doesn't reload the .js animations
_web.LoadUrl ("file://" + path + "/" + _chap.Name);
} else {
_web.LoadUrl ("file://" + path + "/" + _chap.Name);
positions.Add(e.Position);
}
}
};
};
}
public class BWebClient : WebViewClient
{
int _position;
string _path;
Activity _parent;
ViewPager _pager;
string _chapName;
public BWebClient (int position, string Path, Activity Parent, ViewPager Pager, string ChapName){
_position = position;
_parent = Parent;
_path = Path;
_pager = Pager;
_chapName = ChapName;
}
public override void OnPageFinished (WebView view, string url)
{
base.OnPageFinished (view, url);
view.ScrollTo (0, _position);
}
public override bool ShouldOverrideUrlLoading (WebView view, string url)
{
if (url.StartsWith ("navigate")) {
string destination = url.Substring (url.IndexOf ("navigate://") + "navigate://".Length);
int DestinationChapter = Int32.Parse (destination.Substring (0, destination.IndexOf("_")));
int l = destination.IndexOf("_") + 1;
int b = destination.Length - l;
int DestinationPage = Int32.Parse (destination.Substring (l,b));
if (DestinationPage == 0) {
_pager.SetCurrentItem(DestinationChapter ,true);
WebView _web = (WebView)_pager.FindViewWithTag(300 + DestinationChapter);
_web.LoadUrl ("file://" + _path + "/" + _chapName);
}
} else if (url.StartsWith ("pdf")) {
string file_path = System.IO.Path.Combine (_path, url.Substring (url.IndexOf ("pdf://") + "pdf://".Length));
Android.Net.Uri pdfFile = Android.Net.Uri.FromFile (new Java.IO.File (file_path));
Intent pdfIntent = new Intent (Intent.ActionView);
pdfIntent.SetDataAndType (pdfFile, "application/pdf");
_parent.StartActivity (pdfIntent);
}
return true;
}
}
public class AwesomeFragmentAdapter : FragmentPagerAdapter
{
string _path;
string _filename;
int _chapters;
Activity _parent;
FileUtilities _fUtils;
ViewPager _pager;
public AwesomeFragmentAdapter (Android.Support.V4.App.FragmentManager fm,
string path,
string filename,
int chapters,
Activity parent,
FileUtilities FUtils,
ViewPager Pagers): base(fm)
{
_path = path;
_filename = filename;
_chapters = chapters;
_parent = parent;
_fUtils = FUtils;
_pager = Pagers;
}
public override int Count
{
/* --- return chapter count --- */
get { return _chapters;}
}
public override Android.Support.V4.App.Fragment GetItem(int _position)
{
/* --- get specific item --- */
return new AwesomeFragment (_path, _filename, _position, _parent, _fUtils, _pager);
}
}
public class AwesomeFragment : Android.Support.V4.App.Fragment
{
string _path;
WebView web_view;
string _filename;
int _position;
Activity _parent;
BanjiChapter _chap;
FileUtilities _fUtils;
ViewPager _pager;
public AwesomeFragment () {}
public AwesomeFragment (string path,
string filename,
int position,
Activity parent,
FileUtilities FUtils,
ViewPager Pager)
{
_path = path;
_filename = filename;
_position = position;
_parent = parent;
_fUtils = FUtils;
_pager = Pager;
}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
/* --- Create the view --- */
var view = inflater.Inflate (Resource.Layout.BookWebView, container, false);
//return the chapter
_chap = _fUtils.ReturnChapterDetails(_filename, _position);
web_view = view.FindViewById<WebView> (Resource.Id.webview);
web_view.SetWebViewClient(new BanjiWebClient(_position,_path,_parent, _pager, _chap.Name ));
web_view.SetBackgroundColor(Color.Transparent);
web_view.Settings.JavaScriptEnabled = true;
web_view.Tag = 300 + _position;
switch(Resources.DisplayMetrics.DensityDpi){
case Android.Util.DisplayMetricsDensity.Medium:
{
web_view.SetLayerType (LayerType.Software, null);
break;
}
case Android.Util.DisplayMetricsDensity.High:
{
web_view.SetLayerType (LayerType.Hardware, null);
break;
}
case Android.Util.DisplayMetricsDensity.Xhigh:
{
web_view.SetLayerType (LayerType.Hardware, null);
break;
}
}
if (_chap.Background == null) {
view.SetBackgroundColor (Color.White);
} else {
view.SetBackgroundDrawable (new BitmapDrawable (BitmapFactory.DecodeByteArray (_chap.Background, 0, _chap.Background.Length)));
}
if (_position == 0) {
web_view.LoadUrl ("file://" + _path + "/" + _chap.Name);
}
return view;
}
public BChapter GetCurrentBChapter()
{
return _chap;
}
}
EDIT:
BookView.axml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffececec">
<!-- The main content view -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mainView">
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/bookView" />
<ImageButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="left|center"
android:background="#null"
android:id="#+id/menuButton"
/>
</FrameLayout>
<!-- The navigation drawer -->
<LinearLayout
android:id="#+id/left_menu"
android:layout_width="250dp"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
android:layout_gravity="start"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#111">
<Button
android:id="#+id/backStep"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="#style/button_text"
android:background="#ff000000"
android:fitsSystemWindows="false" />
<Space
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/screllArea">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/ThumbLayout" />
</ScrollView>
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
BookWebView
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Use a FragmentStatePagerAdapter instead; it is designed to minimise memory overhead by possibly destroying the fragment when it is not visible, saving only the state information of that fragment.
From the developers docs:
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.
See here for documentation.
I'd like to have a TextView display text, and when you click/longclick on it, a textbox should "show up" and allow editing of said text. When you're done editing (onkey enter i suppose) it should revert back to a textview with the updated text...
I'm wondering if it's feasable to implement such a widget or should I hack a workaround? Tips and suggestions are very welcome.
If you need further idea of what I mean, just go to your e.g. (windows) skype profile and see for yourself.
EDIT:
Clarification: I'm specifically asking for a widget or such which is a textview until clicked on, then transforms to an edittext containing the same text; once done editing it transforms back to a textview representing the new changed text. Thats what i mean by "edittext on demand widget".
But I'm hoping to get something better than
public class Widget {
TextView text;
EditText edit;
String textToRepresent;
//...
}
You have a few different options here.
First you will have to register an onClick or onLongClick to the TextView that you want to make interactive. Just make sure that the user knows it's clickable
Then have your onClick function start a DialogFragment. I like to create show functions. Note that you can use the support libraries here to make your app backwards compatible.
private void showDialog() {
MyDialogFragment dialog = new MyDialogFragment();
dialog.show(getSupportFragmentManager(), "dialog");
}
The DialogFragment is pretty straight forward. In your onCreateView you'll inflate the View that you'll want to display to the user. You can alternatively wrap it with a simple AlertDialogBuilder if you don't want to go custom.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.your_dialog_layout);
mTitleEditText = (TextView) view.findViewById(R.id.title);
mTitleEditText.setOnClickListener(this);
return view;
}
After your findViewByIds set your onClickListeners.
The last thing you have to take care of is getting data back into your original TextView.
You can do this by creating a public method in your Activity that you can call from inside of your DialogFragment. Something like this
#Override
public void onClick(View v) {
int clickedId = v.getId();
if (clickedId == mDoneButton.getId()) {
MyActivity activity = (MyActivity)getActivity();
mTitle = mTitleEditText.getText().toString();
activity.setText(mTitle);
dismiss();
}
}
I would recommend using a DialogFragment because it will handle your life cycle nicely.
However, another option would be to create a new Activity themed to be a dialog
<activity android:theme="#android:style/Theme.Dialog" />
Then you can startActivityForResult to display your dialog and then capture your results in onActivityResult
Here is my solution. I just give you the basic one. Create a TextView in front of EditText and two Button OK,Cancel (You can change to ImageButton like Skype). Change the visiblity of two view. The code is so simple without comment. You can add some null checking according your logic.
public class CompoundTextView extends RelativeLayout implements OnClickListener {
private EditText edt;
private TextView txt;
RelativeLayout layout;
public SkypeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
edt = (EditText) findViewById(R.id.edt);
txt = (TextView) findViewById(R.id.txt_name);
layout = (RelativeLayout) findViewById(R.id.layout);
Button ok = (Button) findViewById(R.id.ok_btn);
Button cancel = (Button) findViewById(R.id.cancel_btn);
ok.setOnClickListener(this);
cancel.setOnClickListener(this);
txt.setOnClickListener(this);
}
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.ok_btn:
String editString = edt.getText().toString();
txt.setText(editString);
layout.setVisibility(View.INVISIBLE);
txt.setVisibility(View.VISIBLE);
break;
case R.id.cancel_btn:
layout.setVisibility(View.INVISIBLE);
txt.setVisibility(View.VISIBLE);
break;
case R.id.txt_name:
txt.setVisibility(View.INVISIBLE);
layout.setVisibility(View.VISIBLE);
break;
}
}
}
Create a XML skypetextview. You can customize font and background to make it's prettier.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="#+id/txt_name"
android:layout_width="fill_parent"
android:layout_height="100dp"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:background="#ff0000" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
android:id="#+id/layout" >
<EditText
android:id="#+id/edt"
android:layout_width="270dp"
android:layout_height="100dp" />
<Button
android:id="#+id/ok_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#id/edt"
android:text="OK" />
<Button
android:id="#+id/cancel_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/ok_btn"
android:layout_toRightOf="#id/edt"
android:text="Cancel" />
</RelativeLayout>
</RelativeLayout>
add (or include) this view to the layout you want.
Example :
public class TestActivity extends Activity {
SkypeTextView test;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater inflate = getLayoutInflater();
test = (SkypeTextView ) inflate.inflate(R.layout.compound_text_view,
null);
setContentView(test);
}
PS: i forgot. You should add some underline format for your textview in order to make user notice it clickable
Let a EditText change its background based on its state(Editable or Frozen). Set a background selector that does this.
Use this selector xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_focused="true" android:drawable="#android:drawable/edit_text"/>
<item android:drawable="#android:drawable/screen_background_light_transparent"/>
</selector>
Like I said on thursday... Yul was pretty close but not quite close. He did have a general same idea but (theoretically) rushed into code too early ;)
The TextBoxOnDemand code supplied below is production-ready. The idea is similar to what I wanted to avoid in the OP and what Yul suggested, but with optimal implementation (using a ViewSwitcher instead of a RelativeLayout for instance)
I gathered the resources needed for this in the following articles:
Creating custom view from xml
Declaring a custom android UI element using XML
Defining custom attrs
How to pass custom component parameters in java and xml
http://kevindion.com/2011/01/custom-xml-attributes-for-android-widgets/
and decided to post them here because the official Google "training" docs are useless and are either obsolete (deprecated) or do not cover what I needed. I hope you don't mind me claiming my own bounty, but this is the solution I wanted (and expected, ergo the bounty).
I guess the code will have to do ;)
TextBoxOnDemand.java:
package com.skype.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.text.SpannableString;
import android.text.style.UnderlineSpan;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnHoverListener;
import android.view.View.OnLongClickListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.ViewSwitcher;
import com.skype.ref.R;
import com.skype.ref.RemoteKeys;
public class TextBoxOnDemand extends ViewSwitcher implements OnClickListener, OnLongClickListener, OnFocusChangeListener, OnHoverListener,
OnEditorActionListener
{
public static final String LOGTAG = "TextBoxOnDemand";
private View btmGuard;
private ImageButton cancel, accept;
private EditText editor;
private RelativeLayout editorLayout;
private TextView face;
private String hint = new String();
private boolean inEditMode = false; //normally this is in textview mode
private boolean inputReady = false;
private String ourData = new String();
private String prefillData = new String();
private String tag = new String(); //usually tag is empty.
private View topGuard;
private int autoLinkMask;// = Linkify.EMAIL_ADDRESSES; //Linkify.ALL;
private ColorStateList textColor, hintColor = null;
public TextBoxOnDemand(Context context)
{
super(context);
build(context);
setEditable(false); //init
}
public TextBoxOnDemand(Context context, AttributeSet attrs)
{
super(context, attrs);
build(context);
init(context, attrs);
setEditable(false); //init
}
public String getPrefillData()
{
return prefillData;
}
public String getTag()
{
return tag;
}
public String getText()
{
Log.d(LOGTAG, "getText() returning '" + ourData + "'");
return ourData;
}
public boolean hasPrefillData()
{
return prefillData.isEmpty();
}
public boolean isEditable()
{
Log.d(LOGTAG, "isEditable() returning " + inEditMode);
return inEditMode;
}
#Override
public void onClick(View v)
{
Log.d(LOGTAG, "onClick(" + v + ")");
if (inEditMode)
{
if (v.equals(accept))
{
if (editor.getEditableText().length() == 0 || editor.getEditableText().length() > 5)
ourData = editor.getEditableText().toString();
setEditable(false);
} else if (v.equals(cancel))
{
setEditable(false);
}
}
}
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event)
{
// Log.d(LOGTAG, "onEditorAction(" + v + ", " + actionId + ", " + event + ") fired!");
Log.d(LOGTAG, "onEditorAction() fired, inputReady = " + inputReady);
if (editor.getEditableText().length() > 0 && editor.getEditableText().length() < (prefillData.length() + 2)) return true; //the user needs to enter something
if (inputReady && (event.getKeyCode() == RemoteKeys.ENTER.keycode() || event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) //always is
{
if (editor.getEditableText().length() > prefillData.length() || editor.getEditableText().length() == 0)
ourData = editor.getEditableText().toString();
setEditable(false);
return false;
}
if ((editor.getEditableText().toString().compareToIgnoreCase(ourData) == 0 || editor.getEditableText().toString()
.compareToIgnoreCase(prefillData) == 0)
&& !inputReady) //means we didn't just keep on holding enter
return true;
else
inputReady = true;
return true;
}
#Override
public void onFocusChange(View v, boolean hasFocus)
{
Log.d(LOGTAG, "onFocusChange(" + v + ", " + hasFocus + ")\tinEditMode = " + inEditMode);
if (inEditMode)
{
if (hasFocus && (v.equals(topGuard) || v.equals(btmGuard)))
{
setEditable(false);
requestFocus();
}
if (hasFocus && (v.equals(editor) || v.equals(accept) || v.equals(cancel)))
{
//do nothing, you should be able to browse freely here
if (ourData.isEmpty() && editor.getEditableText().length() < prefillData.length())
{
Log.d(LOGTAG, "adding prefill, before = " + editor.getEditableText());
editor.setText("");
editor.append(prefillData);
Log.d(LOGTAG, "now is = " + editor.getEditableText());
}
}
} else
{
String text = (ourData.isEmpty()) ? hint : ourData;
ColorStateList color;
if (hintColor != null && ourData.isEmpty())
color = hintColor;
else
color = textColor;
face.setTextColor(color);
if (hasFocus)
{
SpannableString ss = new SpannableString(text);
ss.setSpan(new UnderlineSpan(), 0, text.length(), 0);
face.setText(ss);
} else
face.setText(text);
}
}
#Override
public boolean onHover(View v, MotionEvent event)
{
// Log.d(LOGTAG, "onHover()");
String text = (ourData.isEmpty()) ? hint : ourData;
ColorStateList color;
if (hintColor != null && ourData.isEmpty())
color = hintColor;
else
color = textColor;
face.setTextColor(color);
switch (event.getAction())
{
case MotionEvent.ACTION_HOVER_ENTER:
SpannableString ss = new SpannableString(text);
ss.setSpan(new UnderlineSpan(), 0, text.length(), 0);
face.setText(ss);
break;
case MotionEvent.ACTION_HOVER_EXIT:
face.setText(text);
break;
}
return true;
}
#Override
public boolean onLongClick(View v)
{
Log.d(LOGTAG, "onLongClick()\tinEditMode = " + inEditMode);
if (!inEditMode) //implies that getDisplayedChild() == 0, meaning the textview
{
setEditable(true);
return true;
} else
return false;
}
public void setEditable(boolean value)
{
Log.d(LOGTAG, "setEditable(" + value + ")");
inEditMode = value;
if (inEditMode)
{
//display the editorLayout
face.setOnLongClickListener(null);
face.setOnHoverListener(null);
face.setOnFocusChangeListener(null); //because of GC.
face.setOnClickListener(null);
face.setVisibility(View.GONE);
setDisplayedChild(1);
editorLayout.setVisibility(View.VISIBLE);
editor.setOnFocusChangeListener(this);
editor.setOnEditorActionListener(this);
cancel.setOnClickListener(this);
accept.setOnClickListener(this);
accept.setOnFocusChangeListener(this);
cancel.setOnFocusChangeListener(this);
} else
{
editor.setOnFocusChangeListener(null);
editor.setOnEditorActionListener(null);
cancel.setOnClickListener(null);
accept.setOnClickListener(null);
accept.setOnFocusChangeListener(null);
cancel.setOnFocusChangeListener(null);
editorLayout.setVisibility(View.GONE);
setDisplayedChild(0);
face.setVisibility(View.VISIBLE);
face.setOnLongClickListener(this);
face.setOnHoverListener(this);
face.setOnFocusChangeListener(this);
face.setOnClickListener(this);
face.setFocusable(true);
face.setFocusableInTouchMode(true);
}
updateViews();
}
#Override
public void setNextFocusDownId(int nextFocusDownId)
{
super.setNextFocusDownId(nextFocusDownId);
face.setNextFocusDownId(nextFocusDownId);
// editor.setNextFocusDownId(nextFocusDownId);
accept.setNextFocusDownId(nextFocusDownId);
cancel.setNextFocusDownId(nextFocusDownId);
}
#Override
public void setNextFocusForwardId(int nextFocusForwardId)
{
super.setNextFocusForwardId(nextFocusForwardId);
face.setNextFocusForwardId(nextFocusForwardId);
editor.setNextFocusForwardId(nextFocusForwardId);
}
#Override
public void setNextFocusLeftId(int nextFocusLeftId)
{
super.setNextFocusLeftId(nextFocusLeftId);
face.setNextFocusLeftId(nextFocusLeftId);
editor.setNextFocusLeftId(nextFocusLeftId);
}
#Override
public void setNextFocusRightId(int nextFocusRightId)
{
super.setNextFocusRightId(nextFocusRightId);
face.setNextFocusRightId(nextFocusRightId);
cancel.setNextFocusRightId(nextFocusRightId);
}
#Override
public void setNextFocusUpId(int nextFocusUpId)
{
super.setNextFocusUpId(nextFocusUpId);
face.setNextFocusUpId(nextFocusUpId);
// editor.setNextFocusUpId(nextFocusUpId);
accept.setNextFocusUpId(nextFocusUpId);
cancel.setNextFocusUpId(nextFocusUpId);
}
public void setPrefillData(String prefillData)
{
this.prefillData = new String(prefillData);
}
public String setTag()
{
return tag;
}
public void setText(String text)
{
Log.d(LOGTAG, "setText(" + text + ")");
ourData = text;
updateViews();
}
private void build(Context context)
{
Log.d(LOGTAG, "build()");
addView(View.inflate(context, R.layout.textboxondemand, null));
setFocusable(true);
setFocusableInTouchMode(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setOnFocusChangeListener(this);
setOnLongClickListener(this);
face = (TextView) findViewById(R.id.TBOD_textview);
editorLayout = (RelativeLayout) findViewById(R.id.TBOD_layout);
editor = (EditText) findViewById(R.id.TBOD_edittext);
accept = (ImageButton) findViewById(R.id.TBOD_accept);
cancel = (ImageButton) findViewById(R.id.TBOD_cancel);
topGuard = (View) findViewById(R.id.TBOD_top);
btmGuard = (View) findViewById(R.id.TBOD_bottom);
face.setFocusable(true);
face.setFocusableInTouchMode(true);
face.setOnLongClickListener(this);
face.setOnHoverListener(this);
face.setOnFocusChangeListener(this);
face.setOnClickListener(this);
editor.setOnFocusChangeListener(this);
editor.setOnEditorActionListener(this);
editor.setHint(hint);
editor.setFocusable(true);
editor.setFocusableInTouchMode(true);
accept.setOnClickListener(this);
accept.setOnFocusChangeListener(this);
accept.setFocusable(true);
cancel.setFocusable(true);
cancel.setOnFocusChangeListener(this);
cancel.setOnClickListener(this);
topGuard.setFocusable(true);
topGuard.setOnFocusChangeListener(this);
btmGuard.setFocusable(true);
btmGuard.setOnFocusChangeListener(this);
editor.setNextFocusRightId(R.id.TBOD_accept);
editor.setNextFocusDownId(R.id.TBOD_bottom);
editor.setNextFocusUpId(R.id.TBOD_top);
accept.setNextFocusLeftId(R.id.TBOD_edittext);
accept.setNextFocusRightId(R.id.TBOD_cancel);
cancel.setNextFocusLeftId(R.id.TBOD_accept);
}
private void init(Context context, AttributeSet attrs)
{
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextBoxOnDemand);
//Use a
Log.d(LOGTAG, "init()");
if (a == null) Log.d(LOGTAG, "Did you include 'xmlns:app=\"http://schemas.android.com/apk/res-auto\"' in your root layout?");
final int N = a.getIndexCount();
for (int i = 0; i < N; ++i)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.TextBoxOnDemand_android_hint:
hint = new String(a.getString(attr));
editor.setHint(a.getString(attr));
break;
case R.styleable.TextBoxOnDemand_android_text:
ourData = new String(a.getString(attr));
break;
case R.styleable.TextBoxOnDemand_android_inputType:
int inputType = a.getInt(attr, -1);
if (inputType != -1) editor.setInputType(inputType);
break;
case R.styleable.TextBoxOnDemand_android_textColor:
textColor = a.getColorStateList(attr);
face.setTextColor(textColor);
break;
case R.styleable.TextBoxOnDemand_android_linksClickable:
face.setLinksClickable(a.getBoolean(attr, true));
break;
case R.styleable.TextBoxOnDemand_android_textColorHint:
hintColor = a.getColorStateList(attr);
break;
case R.styleable.TextBoxOnDemand_android_autoLink:
autoLinkMask = a.getInt(attr, 0);
face.setAutoLinkMask(autoLinkMask);
break;
default:
Log.d(LOGTAG, "Skipping attribute " + attr);
}
}
//Don't forget this
a.recycle();
}
private void updateViews()
{
Log.d(LOGTAG, "updateViews()");
// if (getDisplayedChild() == 0) //first child - textview
if (!inEditMode) //first child - textview
{
if (ourData.isEmpty())
{
if (hintColor != null) face.setTextColor(hintColor);
face.setText(hint);
} else
{
face.setTextColor(textColor);
face.setText(ourData);
}
face.setFocusable(true);
face.setFocusableInTouchMode(true);
face.setAutoLinkMask(autoLinkMask);
} else
{ //second child - edittext
editor.setFocusable(true);
editor.setFocusableInTouchMode(true);
if (ourData.startsWith(prefillData) || ourData.length() >= prefillData.length())
editor.setText("");
else
editor.setText(prefillData);
editor.append(ourData);
inputReady = false;
editor.requestFocus();
}
}
public void setAutoLinkMask(LinkifyEnum linkifyEnumConstant)
{
switch (linkifyEnumConstant)
{
case ALL:
autoLinkMask = Linkify.ALL;
break;
case EMAIL_ADDRESSES:
autoLinkMask = Linkify.EMAIL_ADDRESSES;
break;
case MAP_ADDRESSES:
autoLinkMask = Linkify.MAP_ADDRESSES;
break;
case PHONE_NUMBERS:
autoLinkMask = Linkify.PHONE_NUMBERS;
break;
case WEB_URLS:
autoLinkMask = Linkify.WEB_URLS;
break;
case NONE:
default:
autoLinkMask = 0;
break;
}
//set it now
face.setAutoLinkMask(autoLinkMask);
}
public enum LinkifyEnum
{
ALL, EMAIL_ADDRESSES, MAP_ADDRESSES, PHONE_NUMBERS, WEB_URLS, NONE
};
}
I'm still working out some focus-related issues but this works as intended. When I use onFocuslistener 1, you can't focus from one TextBox to the other; when the textbox itself is focusable, I can focus from one to the other just fine, but I cannot inter-focus thru children and thus can't focus on the edittext to type.
the XML file:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/TBOD_textview"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:autoLink="email"
android:focusable="true"
android:focusableInTouchMode="true"
android:linksClickable="true"
android:textAppearance="?android:attr/textAppearanceMedium" />
<RelativeLayout
android:id="#+id/TBOD_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<EditText
android:id="#+id/TBOD_edittext"
android:layout_width="300dp"
android:layout_height="30dp"
android:layout_below="#+id/TBOD_textview"
android:focusable="true"
android:focusableInTouchMode="true"
android:imeOptions="actionDone"
android:inputType="none"
android:maxLines="1"
android:padding="2dp"
android:singleLine="true"
android:textColor="#android:color/black"
android:textSize="14dp" />
<ImageButton
android:id="#+id/TBOD_accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#+id/TBOD_edittext"
android:layout_marginLeft="15dp"
android:layout_toRightOf="#+id/TBOD_edittext"
android:background="#drawable/button_accept_selector" />
<ImageButton
android:id="#+id/TBOD_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#+id/TBOD_edittext"
android:layout_marginLeft="5dp"
android:layout_toRightOf="#+id/TBOD_accept"
android:background="#drawable/button_cancel_selector" />
<View
android:id="#+id/TBOD_top"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_alignParentTop="true"
android:background="#android:color/transparent" />
<View
android:id="#+id/TBOD_bottom"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_alignParentBottom="true"
android:background="#android:color/transparent" />
</RelativeLayout>
</RelativeLayout>
and finally, the attrs.xml file:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TextBoxOnDemand">
<attr name="android:text" />
<attr name="android:inputType" />
<attr name="android:hint" />
<attr name="android:textColor" />
<attr name="android:textColorHint" />
<attr name="android:linksClickable" />
<attr name="android:autoLink" />
</declare-styleable>
</resources>
This is how I used it in my main xml (after including the required namespace add):
<com.shark.widget.TextBoxOnDemand
android:id="#+id/profile_email2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/profile_skypename"
android:layout_below="#+id/profile_email_placeholder"
android:hint="#string/add_email"
android:inputType="textEmailAddress"
android:textColor="#android:color/white"
android:textColorHint="#color/skype_blue" />
EDIT: I've debugged the focus issues. It turns out that giving focus to children is difficult unless you call
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Which kinda remedies the issue but still doesn't solve it. After some while of playing around with the onFocusChange() listener still trying to get the perfect behaviour, I threw in the towel and put in added two focus guards. I realized I cannot track the loss of focus only on my container (due to it never receiving focus) but I might as well track the idea of wanting to move away from the edit field... So i went the dirty route and added two invisible bar-like views to sandwitch the edittext in between. Once they got the focus, I could hide the component and ensure they transition properly.
And there it is, now it works as it should. Thanks to all who participated.
EDIT3: final polished version, i dumped the custom tags because they simply don't work reliably enough. Lesson to be learned: if there is an android tag for something, don't bother cloning it.