I have an image on my screen and want the user to touch a specific region of the image. I have placed an invisible button on top of the region I want the user to click and set an onClickListener on the same.
From what I read, INVISIBLE buttons still acquire the space they are at, unlike GONE buttons.
However, the onClick method is not invoked if I click the region. Is there a fact I am missing or an alternate way to achieve this?
public class InteractiveFragment extends Fragment implements View.OnClickListener, OnClickableAreaClickedListener {
private Button btnEye;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_interactive, container, false);
btnEye = view.findViewById(R.id.btnEye);
btnEye.setOnClickListener(this);
btnEye.setVisibility(View.INVISIBLE);
}
#Override
public void onClick(View v) {
Toast.makeText(getContext(), "Correct", Toast.LENGTH_SHORT).show();
System.out.println("method invoked");
}
]
you should know the android event dispatch mechanism. we can see
it in the ViewGrouop dispatchOnTouchEvent method source code.
here is the core code:
//child is the view
if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// the canReceivePointerEvents method in view
protected boolean canReceivePointerEvents() {
return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
}
we can get when the view is INVISBLE, it can not receive the event, the parent don't dispatch event to it.
so if you want to respond to click event, you have to set it VISIBLE, set the button background transparent.
btn.setVisibility(View.VISIBLE);
btn.setbackgroundcolor(Color.Transparent);
Set the background to transparent and added a finite size to the button. Courtesy #Sam
Used the following xml config for the button
<Button
android:id="#+id/btnEye"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="#android:color/transparent"
tools:visibility="visible" />
Related
I am modifying the kokos project with JavaFX and Android, with the jfxmobile plugin and when I add a textfield the on-screen keyboard does not appear and I can not modify the text.
mytextfield is an object of the TextField class of JavaFX:
#FXML
public void initialize(){
counter = 0;
mytextfield.setStyle( "-fx-background-color:#FFFF00; -fx-skin: \"com.sun.javafx.scene.control.skin.TextFieldSkinAndroid\"; ");
mytextfield.requestFocus();
}
public void onButtonClick(){
counter++;
clickLabel.setText("You've clicked this button " + counter + " times!");
}
What could be happening?
I've tested the Kokos project, modifying the JavaFX application class to include a JavaFX TextField:
#Override
public void start (Stage stage) throws Exception {
final Button b = new Button("Click JavaFX");
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
b.setText("Clicked");
}
});
Screen primaryScreen = Screen.getPrimary();
Rectangle2D visualBounds = primaryScreen.getVisualBounds();
double width = visualBounds.getWidth();
double height = visualBounds.getHeight();
VBox box = new VBox(10, b, new TextField());
box.setAlignment(Pos.CENTER);
Scene s = new Scene(box, width, height);
stage.setScene(s);
stage.show();
}
and I can reproduce the issue: the soft keyboard doesn't show up.
For starters you don't need to set the -fx-skin property for the TextField, it will be applied internally.
If you check the logs, with adb logcat or from AndroidStudio, you will notice that there is a call to show the keyboard when the textField gets the focus, and another one to hide it when it loses it:
V/FXEntity: Called notify_showIME
V/FXEntity: Done calling notify_showIME
...
V/FXEntity: Called notify_hideIME
V/FXEntity: Done Calling notify_hideIME
This means that the JavaFX TextField actually do the right calls to show and hide the keyboard, but something is failing.
After some debugging, I noticed that the activity_main.xml was using this for the fragment definition:
android:name="android.webkit.WebViewFragment"
which corresponds with a built-in fragment intended to show a WebView.
That is not what we need here, so I created a fragment:
public class MyFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fx_fragment, container, false);
return view;
}
}
based on the existing fx_fragment.xml, and modified activity_main.xml accordingly:
<fragment
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:name="javafxports.org.kokos.MyFragment"
android:id="#+id/fragment"
android:layout_gravity="left|top"
tools:layout="#layout/fx_fragment" />
And this is it, now when you run the app and the textField gets the focus, the keyboard shows up, and you can type in it.
I've got EditTexts in my rows in a ListView. When I tap on one of the EditTexts the soft keyboard appears and the focus jumps to the first EditText in the list instead of staying in the field where I tapped.
Here is a video of it:
https://youtu.be/ZwuFrX-WWBo
I created a completely stripped down app to demonstrate the problem. The full code is here: https://pastebin.com/YT8rxqKa
I'm not doing anything to alter the focus in my code:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.cell_textfield, parent, false);
}
TextView label = (TextView) convertView.findViewById(R.id.textview1);
EditText textfield = (EditText) convertView.findViewById(R.id.textview2);
String text = String.format("Row %d", position);
label.setText(text);
textfield.setText(text);
return convertView;
}
I found another post on StackOverflow giving a workaround for this dumb Android behavior, which involves putting an OnFocusChangedListener on all of the textfields so they can retake focus if it's taken from them improperly.
That worked to regain focus, but then I discovered that when a textfield retakes focus the cursor ends up at the start of the text instead of end, which is unnatural and annoying to my users.
Here is a video of that:
https://youtu.be/A35wLqbuIac
Here's the code for that OnFocusChangeListener. It works to fight the stupid Android behavior of moving focus, but the cursor is misplaced after it regains focus.
View.OnFocusChangeListener onFocusChangeListener = new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View view, boolean hasFocus) {
long t = System.currentTimeMillis();
long delta = t - focusTime;
if (hasFocus) { // gained focus
if (delta > minDeltaForReFocus) {
focusTime = t;
focusTarget = view;
}
}
else { // lost focus
if (delta <= minDeltaForReFocus && view == focusTarget) {
focusTarget.post(new Runnable() { // reset focus to target
public void run() {
Log.d("BA", "requesting focus");
focusTarget.requestFocus();
}
});
}
}
}
};
I hate having to put a bandaid on a bandaid on a bandaid to try to get Android to just behave as it would naturally be expected to behave, but I'll take what I can get.
1) Is there something I can do to fix this problem at the source and not have to have the OnFocusChangeListener at all?
2) If (1) isn't possible, then how can I make sure that when I force focus back to the correct field that I make sure the cursor is placed at the end? I tried using setSelection() right after requestFocus() but since the textfield wasn't yet focused the selection is ignored.
Here was my "solution." In short: ListViews are stupid and will always be a total nightmare when EditTexts are involved, so I changed my Fragment/Adapter code to be able to adapt to either a ListView layout or a ScrollView layout. It only works if you have a small number of rows, because the scrollview implementation isn't able to take advantage of lazy-loading and view recycling. Thankfully, any situation wherein I want EditTexts in a ListView, I rarely have more than 20 rows or so.
When inflating my view in my BaseListFragment, I get my layout id via a method that relies on a hasTextFields() method:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(getLayoutId(), container, false);
return view;
}
public boolean hasTextfields() {
return false;
}
public int getLayoutId() {
if (hasTextfields()) {
return R.layout.scrollfragment;
} else {
return R.layout.listfragment;
}
}
In my various subclasses of my BaseListFragment, if I need to have an EditText in one of my fields, I just override the hasTextFields() method to return true and then my fragment/adapter switchs over to using the basic scrollview implementation.
From there, it's a matter of making sure that the Adapter handles the standard ListView actions for both the ListView and the ScrollView scenarios. Like this:
public void notifyDataSetChanged() {
// If scrollContainer is not null, that means we're in a ScrollView setup
if (this.scrollContainer != null) {
// intentionally not calling super
this.scrollContainer.removeAllViews();
this.setupRows();
} else {
// use the real ListView
super.notifyDataSetChanged();
}
}
public void setupRows() {
for (int i = 0; i < this.getCount(); i++) {
View view = this.getView(i, null, this.scrollContainer);
view.setOnClickListener(myItemClickListener);
this.scrollContainer.addView(view);
}
}
One issue that the click listener presented is that a ListView wants an AdapterView.OnItemClickListener, but arbitrary Views inside a ScrollView want a simple View.OnClickListener. So, I made my ItemClickListener also implement View.OnClickListener and then just dispatched the OnClick to the OnItemClick method:
public class MyItemClickListener implements AdapterView.OnItemClickListener, View.OnClickListener {
#Override
public void onClick(View v) {
// You can either have your Adapter set the tag on the View to be its position
// or you could have your click listener use v.getParent() and iterate through
// the children to find the position. I find its faster and easier to have my
// adapter set the Tag on the view.
int position = v.getTag();
this.onItemClick(null, v, config.getPosition(), 0);
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// ...
}
}
Then in MyEditTextListFragment, I create the adapter like this:
listener = createClickListener();
adapter = createListAdapter();
if (scrollContainer != null) {
adapter.setScrollContainer(scrollContainer);
adapter.setMenuItemClickListener(listener);
adapter.setupRows();
} else {
getListView().setOnItemClickListener(listener);
getListView().setAdapter(adapter);
}
Here is my scrollfragment.xml for reference:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff"
android:clickable="true"
>
<!--
The following LinearLayout as a focus catcher that won't cause the keyboard to
show without it, the virtual keyboard shows up immediately/always which means we
never get to the enjoy the full size of our screen while scrolling, and
that sucks.
-->
<LinearLayout
android:focusable="true"
android:focusableInTouchMode="true"
android:layout_width="0px"
android:layout_height="0px"/>
<!--
This ListView is still included in the layout but set to visibility=gone. List
fragments require a standard ListView in the layout, so this gets us past that
check and allows us to use the same adapter code in both listview and scrollview
situations.
-->
<ListView android:id="#id/android:list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawSelectorOnTop="false"
android:background="#null"
android:layout_alignParentTop="true"
android:descendantFocusability="afterDescendants"
android:visibility="gone"
/>
<!--
This scrollview will act as our fake listview so that we don't have to deal with
all the stupid crap that comes along with having EditTexts inside a ListView.
-->
<ScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="afterDescendants"
>
<LinearLayout
android:id="#+id/scrollContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
</LinearLayout>
</ScrollView>
</RelativeLayout>
Try this once, it worked for me:
public void setCursorPosition() {
focusTarget.requestFocus();
focusTarget.setCursorVisible(true);
other.setCursorVisible(false);
} else {
other.setCursorVisible(true);
focusTarget.setCursorVisible(false);
}
}
In my app I need to be able to change the background color of a button and back to the default color. Changing the color to a custom color works, but my code for reversing the process has given me issues.
My button code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.custom_practice, container, false);
mNomButton = (Button) view.findViewById(R.id.custom_practice_nom_button);
mNomButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mNomIsSelected = !mNomIsSelected;
mNomButton.setBackgroundResource(mNomIsSelected ? R.color.buttonSelected : android.R.drawable.btn_default);
updateView(mNomButton);
}
});
return view;
}
When I reset the button resource I end up with a bordered button where I had borderless before:
On layout inflate:
OnClick the first time:
OnClick the second time:
I would like to avoid having to create a custom drawable that mimics the flat button. Is there a way to get the default borderless button resource?
when u set background in second time set it by getting background from another button which has the default background so for ex let's say button in red is b1 and button in default is b2
Then code to set background for b1 to become default is
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
b1.setBackground(b2.getBackground());
else b1.setBackgroundDrawable(b2.getBackground());
It took some time because many of the answers included deprecated code, but I have a solution to my issue. Using the suggestion of Mohamed I grabbed the default Drawable value of one of the buttons and stored it. The biggest issue was finding a way for both the default color and my color from colors.xml to be the same type for my ternary if to work.
private Drawable mDefaultButtonColor;
private Drawable mSelectedButtonColor;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mNomButton = (Button) view.findViewById(R.id.custom_practice_nom_button);
mNomButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mNomIsSelected = toggleButton(mNomButton, mNomIsSelected);
}
}
mDefaultButtonColor = ((Drawable) mNomButton.getBackground());
mSelectedButtonColor = ContextCompat.getDrawable(getActivity(), R.color.buttonSelected);
return view;
}
private boolean toggleButton(Button button, boolean isSelected) {
isSelected = !isSelected;
button.setBackground(isSelected ? mSelectedButtonColor : mDefaultButtonColor);
return isSelected;
}
I have a button which is called Check, I want it to be invisible and visible as I click each time on it, as If its visible and I clicked it will become invisible and verse vies !
But my code doesn't work ! any ideas ?
Button Check ;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings);
Check = (Button)findViewById(R.id.checkButton);
Check.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View View) {
if (View.getVisibility() == android.view.View.VISIBLE)
View.setVisibility(android.view.View.INVISIBLE);
else if (View.getVisibility() == android.view.View.INVISIBLE)
View.setVisibility(android.view.View.VISIBLE);
}
});
In my activity its visible at the beginning and when I click on it, it become invisible, BUT when I click again it stays invisible !
Change your code to this,
Check.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (v.isShown())
v.setVisibility(View.INVISIBLE);
else
v.setVisibility(View.VISIBLE);
}
But i think problem is, when button goes invisible, you are not getting any click event on it. First make sure that onClick method get call when button is invisible.
An invisible button will not dispatch any interaction event. So instead of setting button's visibility to the invisible, you can set a transparent or blank background or something like that.
But i personally believe, you should change your use-case because why one will click on the invisible button.
Try This:
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="abcd" >
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:onClick="abc"
android:text="Button" />
</LinearLayout>
public void abc(View v) {
v.setVisibility(View.INVISIBLE);
}
public void abcd(View v) {
v.findViewById(R.id.button1).setVisibility(View.VISIBLE);
}
Invisible Items don't receive on-click event. So the only way you can receive a click on invisible is by receiving on some other view in place of the invisible view. The above solution wraps the button in a layout, so when button is invisible the on-click is passed on to layout, which handles the event and do accordingly. If you have a high usage of such layout you can also create a custom button with above mechanism.
We generate several ListViews that hold info for a user to filter information in another fragment. It works fine, unless you pause and resume the app (say, backgrounding it, or locking the screen). Once you do that, the list can be scrolled, but not clicked.
List generating code:
private View addList(LayoutInflater inflater, ViewGroup container, final FilterValue.SearchCategory type, final String[] labels) {
ArrayAdapter<String> adapter = generateArrayAdapter(inflater, labels, type);
if(adapter == null) {
return null;
}
filterAdapters.add(adapter);
ListView list = (ListView) inflater.inflate(R.layout.on_demand_filter_list, container, false);
list.setAdapter(adapter);
list.setItemsCanFocus(false);
list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
list.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
list.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(final View view, final MotionEvent motionEvent) {
LOG.d(TAG, "NO TOUCHING!");
return false; //To change body of implemented methods use File | Settings | File Templates.
}
});
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
LOG.d(TAG, "onItemClick!");
CheckedTextView textView = (CheckedTextView) view.findViewById(R.id.title);
textView.toggle();
if (textView.isChecked()) {
filterValue.addToSelectedList(labels[i], type);
} else {
filterValue.removeFromSelectedList(labels[i], type);
}
}
});
list.setAdapter(adapter);
list.setVisibility(View.GONE);
filterListContainer.addView(list);
return list;
}
The onTouch listener only exists to ensure the Touch is received. (It is.) The DescendantFocusability appears to have no effect, this bug exists before and after it was added.
Each is tied to a button that shows or hides the list.
titleHeader.setOnClickListener(new View.OnClickListener() {
public void onClick(View clickedView) {
closeNetworkList();
closeGenreList();
titlesOpen = !titlesOpen;
ImageView indicator = (ImageView) clickedView.findViewById(R.id.filter_expansion_indicator_icon);
if (indicator != null) {
if (titlesOpen) {
indicator.setImageResource(R.drawable.arrow_filter_up);
} else {
indicator.setImageResource(R.drawable.arrow_filter_down);
}
}
if (titlesOpen) {
titlesListView.setVisibility(View.VISIBLE);
} else {
titlesListView.setVisibility(View.GONE);
}
}
});
Tapping this button to hide and then show the listView (which was generated with addList) resets something, and the items can be clicked again.
XML for an item row:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:focusableInTouchMode="false"
android:padding="8dp"
android:orientation="horizontal">
<CheckedTextView
android:id="#+id/title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#drawable/on_demand_filter_checked_text_sel"
android:gravity="center_vertical"
android:paddingLeft="76dp"
android:focusable="false"
android:focusableInTouchMode="false"
android:drawableLeft="#drawable/checkbox_sel"
android:drawablePadding="14dp"
style="#style/LargeRegular"/>
</LinearLayout>
The focusables are new additions, but neither worked. The problem occurred before they were added.
The ListView itself:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/list"
android:layout_width="fill_parent"
android:layout_height="275dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:divider="#color/Transparent"
android:descendantFocusability="blocksDescendants"
android:cacheColorHint="#ffffff"/>
I am at my absolute wits' end. No one on my team has a sensible solution to this. It works fine, right up until you pause and resume. We do absolutely nothing that touches the views in resume or pause. Can anyone help? I can provide more detail as needed.
I had similar problem with my app (extended SurfaceView which lost touch events after resume) and resolved it by calling the setFocusable( true ) in the onResume() implementation. Apparently the view didn't get the focus and therefore did not receive the touch events. Not sure whether this is the case here, but worth trying.
Remembered that I had had a similar problem with fragment activities. I had a case when layout requests were blocked, they did not cause actual layout traverse.
I've fixed it in Enroscar library (BaseFragment class) with the following snippet of code in a fragment class:
#Override
public void onStart() {
// ... other staff ...
super.onStart();
/*
XXX I don't know the reason but sometimes after coming back here from other activity all layout requests are blocked. :(
It looks like some concurrency issue or a views tree traversal bug
*/
final View contentView = getActivity().findViewById(android.R.id.content);
if (contentView != null) {
final ViewParent root = contentView.getParent();
if (contentView.isLayoutRequested() && !root.isLayoutRequested()) {
if (DebugFlags.DEBUG_GUI) { Log.i("View", "fix layout request"); }
root.requestLayout();
}
}
}