I create views dynamically, and I handle their click events normally, like this :
myView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
v.setBackgroundColor(0xaaffffff);
}
});
But this code does not handle the onRelease event, so the view remains with white background, after the click.
So how can I set the background to another value when the clicked state is over ?
Thanks in advance !
Edit:
OnTouchListener does the exact same thing. I guess I need something like onReleaseListener ?
For that you need to use an onTouchListener => documentation
Refer this tutorial on how to use this.
Example:
myView.setOnTouchListener(
new View.OnTouchListener() {
public boolean onTouch(View myView, MotionEvent event) {
int action = event.getAction();
if (action==MotionEvent.ACTION_MOVE)
{
myView.setBackgroundColor(Color.BLUE);
}
if (action==MotionEvent.ACTION_UP)
{
myView.setBackgroundColor(Color.RED);
}
if (action==MotionEvent.ACTION_DOWN)
{
myView.setBackgroundColor(Color.YELLOW);
}
// TODO Auto-generated method stub
return true;
}
}
Android has a selector for this. Define it in a xml file in your drawable folder and use it as background:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false" android:drawable="#drawable/YOURDRAWABLE" />
<item android:state_pressed="true" android:drawable="#drawable/YOURDRAWABLE_WHEN_CLICKED" />
</selector>
Check the DOCUMENTATION
State lists are meant to be for style updates on state changes. Check this out.
http://developer.android.com/guide/topics/resources/drawable-resource.html#StateList
You dont need to go for such cómplicated method for capturing a "click" event. Just for this method :-
//Inside on touch listener of course :-
KOTLIN :-
if(event.action == MotionEvent.ACTION_UP && event.action != MotionEvent.ACTION_MOVE) {
// Click has been made...
// Some code
}
JAVA :-
Just replace event.action with event.getAction()
This works for me 😉
Related
I'm having a problem with my Button staying in a highlighted state, after doing the following:
public class MainActivity extends AppCompatActivity {
#SuppressLint("ClickableViewAccessibility")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("Test", "calling onClick");
}
});
button.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
v.invalidate();
break;
}
case MotionEvent.ACTION_UP: {
v.getBackground().clearColorFilter();
v.invalidate();
v.performClick();
Log.d("Test", "Performing click");
return true;
}
}
return false;
}
});
}
}
Concerning the code above, when using it, I'm expecting the button click to be handled by the touch, and by returning "true" the handling should stop at the touchListener.
But this is not the case. The button stays in a highlighted state, even though the click is being called.
What I get is:
Test - calling onClick
Test - Performing click
on the other hand, if I'm using the following code, the button is clicked, same prints, but the button doesn't end up stuck in a highlighted state:
public class MainActivity extends AppCompatActivity {
#SuppressLint("ClickableViewAccessibility")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("Test", "calling onClick");
}
});
button.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
v.invalidate();
break;
}
case MotionEvent.ACTION_UP: {
v.getBackground().clearColorFilter();
v.invalidate();
// v.performClick();
Log.d("Test", "Performing click");
return false;
}
}
return false;
}
});
}
}
I'm a bit confused as to what's the responder chain to the touch event. My guess is that it's:
1) TouchListener
2) ClickListener
3) ParentViews
Can someone confirm this as well?
Such customizations need no programmatically modifications. You can do it simply in xml files. First of all, delete the setOnTouchListener method that you provide in the onCreate entirely. Next, define a selector color in the res/color directory like the following. (if the directory doesn't exist, create it)
res/color/button_tint_color.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#e0f47521" android:state_pressed="true" />
<item android:color="?attr/colorButtonNormal" android:state_pressed="false" />
</selector>
Now, set it to the button's app:backgroundTint attribute:
<androidx.appcompat.widget.AppCompatButton
android:id="#+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:backgroundTint="#color/button_tint_color" />
Visual Result:
EDITED: (to address touch event issue)
From an overall point of view, the flow of the touch event starts from the Activity, then flows down to the layout (from the parent to the child layouts), and then to the views. (LTR flow in the following picture)
When the touch event reaches the target view, the view can handle the event then decide to pass it to the prior layouts/activity or not (returning false of true in onTouch method). (RTL flow in the above picture)
Now let's take a look at the View's source code to gain a deeper insight into the touch event flows. By taking a look at the implementation of the dispatchTouchEvent, we'd see that if you set an OnTouchListener to the view and then return true in its onTouch method, the onTouchEvent of the view won't be called.
public boolean dispatchTouchEvent(MotionEvent event) {
// removed lines for conciseness...
boolean result = false;
// removed lines for conciseness...
if (onFilterTouchEventForSecurity(event)) {
// removed lines for conciseness...
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) { // <== right here!
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
// removed lines for conciseness...
return result;
}
Now, look at the onTouchEvent method where the event action is MotionEvent.ACTION_UP. We see that perform-click action happens there. So, returning true in the OnTouchListener's onTouch and consequently not calling the onTouchEvent, causes not calling the OnClickListener's onClick.
There is another issue with not calling the onTouchEvent, which is related to the pressed-state and you mentioned in the question. As we can see in the below code block, there is an instance of UnsetPressedState that calls setPressed(false) when it runs. The result of not calling setPressed(false) is that the view gets stuck in the pressed state and its drawable state doesn't change.
public boolean onTouchEvent(MotionEvent event) {
// removed lines for conciseness...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
// removed lines for conciseness...
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// removed lines for conciseness...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// removed lines for conciseness...
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
// removed lines for conciseness...
}
// removed lines for conciseness...
break;
// removed lines for conciseness...
}
return true;
}
return false;
}
UnsetPressedState:
private final class UnsetPressedState implements Runnable {
#Override
public void run() {
setPressed(false);
}
}
Regarding the above descriptions, you can change the code by calling setPressed(false) yourself to change the drawable state where the event action is MotionEvent.ACTION_UP:
button.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
v.invalidate();
break;
}
case MotionEvent.ACTION_UP: {
v.getBackground().clearColorFilter();
// v.invalidate();
v.setPressed(false);
v.performClick();
Log.d("Test", "Performing click");
return true;
}
}
return false;
}
});
You are messing around touch and focus events. Let start with understanding behavior with same color. By default, there is Selector assigned as background to the Button in Android. So simply changing background color, make is static (color will not change). But it's not a native behavior.
Selector might look like this one.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_focused="true"
android:state_pressed="true"
android:drawable="#drawable/bgalt" />
<item
android:state_focused="false"
android:state_pressed="true"
android:drawable="#drawable/bgalt" />
<item android:drawable="#drawable/bgnorm" />
</selector>
As you can see above, there is state focused and state pressed. By setting onTouchListener you will handle touch events, which have nothing to do with focus.
Selector of the Button should replace focus event with touch during click event on the button. But in first part of your code you intercepted events for the touch (returning true from callback). Color change cannot proceed further and is freezing with same color . And that is why second variant (without interception) are working fine and that is your confusion.
UPDATE
All you need to do it's to change behavior and color for the Selector. For ex. by using next background for the Button. AND remove onTouchListener from your implementation at all.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="#color/color_pressed" />
<item android:drawable="#color/color_normal" />
</selector>
if you assign a background to the button, it wont change the color on click.
<color name="myColor">#000000</color>
and set it as backgrount to your button
android:background="#color/myColor"
you can just use material Chips for instead of Button view.
refer : https://material.io/develop/android/components/chip
there they handle those hililghted events and you can customize with applying the themes.
I'm new in Android app developing and I realized that, at the end of the day, what truly matters to the end user is the App's UI.
I always try to do my best when it comes to UI's, but I always end up having some troubles.
In particular, one of the main problems I always have and I don't know how to fix, is that when I set a custom background color or image to some button, the click effect disappears. So you click the button and although it obviously works, it's pretty unpleasant to see how it does nothing graphically.
I'd like to know if there's some way to get the original effect back, or set some click effect myself programmatically.
Thanks in advance.
Save this as a drawable and set as the background:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#212121"/>
<corners android:radius="5dp"/>
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="#424242"/>
<corners android:radius="5dp"/>
</shape>
</item>
</selector>
This is just a simple example. You can create more complicated ones yourself.
Lets say you have a button or a TextView or any other view. and you want it to change its color during touch event and also to do something after the click:
button.setOnTouchListener(new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE)
{
v.setBackgroundColor(Color.parseColor("#000000"));
}
if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL)
{
v.setBackgroundColor(Color.parseColor("#ffffff"));
}
return false;
}
});
and then add click listener:
button .setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
do some stuff...
}
});
Thanks to Amir Horev's answer I could figure out what I really needed. I based my solution on his answer but instead of calling the setBackgroundColor() method, I used the setColorFilter() one instead from the background of the view, to trigger both the ACTION_DOWN and ACTION_UP events. So now I can basically set a color filter to any button. This is how the code finally looked like:
button.setOnTouchListener(new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
v.getBackground().setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0xFFAA0000));
}
if (event.getAction() == MotionEvent.ACTION_UP)
{
v.getBackground().setColorFilter(new LightingColorFilter(0xFFFFFFFF, 000000000));
}
return false;
}
});
I also wrote a function like public void triggerActionDownEvent(Button button); so I can call this funciton instead of writing all of the previous code for every button. Now I'll have to ensure that no more than one button is pressed at anytime.
I suggest you to set the color or the image programmatically. You can use:
Button button = (Button)findViewById(R.id.myButton);
button.setImageSource(R.drawable.image);
I have a little problem. I have button with textview beside it. I want to touch the button when the textview is touched (effect: button is highlighted when I touch the button). Is there some easy way to do this? I cannot find any appriopriate function.
Edit: Okay, I've fixed my problem. It is:
hilfeText.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent me) {
int action = me.getAction();
if(action == MotionEvent.ACTION_DOWN) {
hilfe.setPressed(true);
return true;
} else if (action == MotionEvent.ACTION_UP) {
hilfe.setPressed(false);
return true;
}
return false;
}
});
You can define the click and focus mode of a button in xml file
like
`
<item android:drawable="#drawable/recordings_icon" android:state_enabled="false"></item>
<item android:drawable="#drawable/recordings_glow" android:state_enabled="true" android:state_pressed="true"/>
<item android:drawable="#drawable/recordings_shadow" android:state_enabled="true" android:state_focused="true"/>
<item android:drawable="#drawable/recordings_icon" android:state_enabled="true"/>
you can put this file in a directory name drawables
#drawable/recordings_icon is an image file
you just declare this filename as background for button layout xml file
<Button
android:id="#+id/buttonActivate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/xmlfilename" />
You can call performClick() method using Button reference inside onClick of TextView.
example-
myTextView.setOnClickListner(){
public void onClick(){
myButton.performClick();
--------------code
}
}
Setup an onClickListener for your Button and TextView and in the onClick(), let them both call the same function. Simple.
This might do the trick
TextView v = ...;
final Button b = ...;
v.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
b.setFocusableInTouchMode(true);
b.requestFocus();
b.performClick();
}
});
i am working on android.
i am creating a login button. i want that whenever i press login button then text color of that button should be changed.
and when this button pressed then login functionality should be performed.
for this i am coding like this:-
button_login.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent arg1) {
// TODO Auto-generated method stub
int action = arg1.getAction();
if(action == MotionEvent.ACTION_DOWN) {
button_login.setTextColor(Color.parseColor("#163D74"));
return true;
} else if (action == MotionEvent.ACTION_UP) {
button_login.setTextColor(Color.parseColor("#FFFFFF"));
return true;
}
return false;
}
});
button_login.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View v)
{
// checking the functionality of login
}
});
but only onTouchListener is working. login functionality is not working.
please suggest me what mistake i have done. and how can i implement these both functionalities. means how can i change the color of text of button and how can i perform login functionality.
Thank you in advance.
You should just declare your login functionality when action is UP.
button_login.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent arg1) {
// TODO Auto-generated method stub
int action = arg1.getAction();
if(action == MotionEvent.ACTION_DOWN) {
button_login.setTextColor(Color.parseColor("#163D74"));
return true;
} else if (action == MotionEvent.ACTION_UP) {
// check logine functionality.
button_login.setTextColor(Color.parseColor("#FFFFFF"));
return true;
}
return false;
}
});
Its does the same thing with the same logic.
In onTouch method, you used return true; that means
True if the listener has consumed the event, false otherwise.
So you need to return false if you need it to consumed by other listeners
I think what you want to do is to change the UI state of button while pressing. For that you don't have to implement on touch... Just write an xml and put it on the src tag of button.
A. Create a new tag on colors.xml.. You can add colors as drawables.... which you will need on step two.
Write something like this in colors.xml
<drawable name="red">#0000ff</drawable>
B. Create a new xml in drawables folder, Name it what you like
write this in the xml, Here you can replace drawables here with drawable you can create with help of step A.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/android_pressed"
android:state_pressed="true" />
<item android:drawable="#drawable/android_focused"
android:state_focused="true" />
<item android:drawable="#drawable/android_normal" />
</selector>
This code is copied from here.. See Custom Button section.
C. Add this XML you created in step B as value of src attribute of your button, you have replaced of what I understood your onTouch is doing...
I've created a custom button in my Android app that has basically two different views. There is an image for when the button isn't being pressed and another image for use while it is being pressed. Below is how I've implemented the button and how it responds to the user.
private void registerListeners() {
calcButton.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
calcButton.requestFocusFromTouch();
calcButton.setImageResource(R.drawable.calc_button_pressed);
return false;
}
});
calcButton.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
calcButton.setImageResource(R.drawable.calc_button_not_pressed);
}
});
calcButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mathCalculation();
calcButton.setImageResource(R.drawable.calc_button_not_pressed);
}
});
}
My problem is that there is a "bug" where if the user touches the button and drags their figure off the button the button stay pressed down. The one work around I've implemented above is the "setOnFocusChangeListener" so once the user select something it else it will pop back up.
I want to have it so the button pops back out when the user drags there touched figure off the button.
All suggestions are greatly appreciated!!!
Thank you,
You don't need to write a separate button class to get that behavior, you can implement it through a drawable xml. Have this in your xml and set it as the background for your button:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="#drawable/calc_button_pressed" />
<item android:drawable="#drawable/calc_button_not_pressed" />
</selector>
In your onTouch method, check for the event action.
If it's MotionEvent.ACTION_DOWN, then proceed as you have.
If it's ACTION_UP or ACTION_CANCEL, then call calcButton.setImageResource(R.drawable.calc_button_not_pressed);
http://developer.android.com/reference/android/view/MotionEvent.html
You Could use use onTouch. Then use the ACTION_OUTSIDE which is fired when the users touch moves out the the bound of the view.
Edit 1: to be more specific:
OnTouchListener(MotionEvent e) {
switch(e.getAction()) {
case MotionEvent.ACTION_OUTSIDE: // switch the image if the button
}
}
It's because the click event "happens" only when touching and releasing. If you're touching and moving outside that's not a click.
To do what you want use a StateListDrawable, it's made just for that.