In my activity, I have a editText field. When the user taps on it, the editText gains the focus and the keyboard appears. Now, when the user presses the hardware back button on the phone, the keyboard disappears but the cursor remains in the Edittext, i. e., it still has the focus. Is it possible to make the EditText lose focus when back button is pressed? I tried using the following code but it didn't work:
#Override
public void onBackPressed() {
vibrator.vibrate(Constants.DEFAULT_VIBRATE_TIME);
myEditText.clearFocus();
super.onBackPressed();
}
Just extend EditText:
public class EditTextV2 extends EditText
{
public EditTextV2( Context context )
{
super( context );
}
public EditTextV2( Context context, AttributeSet attribute_set )
{
super( context, attribute_set );
}
public EditTextV2( Context context, AttributeSet attribute_set, int def_style_attribute )
{
super( context, attribute_set, def_style_attribute );
}
#Override
public boolean onKeyPreIme( int key_code, KeyEvent event )
{
if ( key_code == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP )
this.clearFocus();
return super.onKeyPreIme( key_code, event );
}
}
And in the xml just use <yourPackage.EditTextV2> instead of <EditText>.
Note: You may need to add/remove constructors to this class depending on the min API you're supporting. I suggest just adding them all and removing the ones whose super() calls get underlined in red.
For anyone using Kotlin and Material Design, you can use this:
class ClearFocusEditText: TextInputEditText {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onKeyPreIme(keyCode: Int, event: KeyEvent?): Boolean {
if(keyCode == KeyEvent.KEYCODE_BACK) {
clearFocus()
}
return super.onKeyPreIme(keyCode, event)
}
}
You can make another of your Views focusable, for example an ImageView. Be sure to make it focusable in touch mode, using setFocusableInTouchMode(true) and on onResume() make that View to requestFocus().
Also you can create a dummy View with 0 dimensions and perform same steps described above.
I hope this helps.
Add view like the following higher than your EditText:
<LinearLayout
android:layout_width="0px"
android:layout_height="0px"
android:focusable="true"
android:focusableInTouchMode="true" />
Also to hide keyboard add this in onBackPressed():
((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(myEditText.getWindowToken(), 0);
I am providing kotlin equivalent code to Kacy answer with some edit
class CustomSearch #JvmOverloads constructor(context: Context, attrs: AttributeSet? =
null, defStyle: Int = 0):AppCompatEditText(context, attrs, defStyle) {
override fun onKeyPreIme(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK){
val imm:InputMethodManager = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(this.windowToken, 0)
this.clearFocus()
//this.findFocus()
}
return true
}
Note: I am returning true and this is solving my problem i.e removing focus from edit text and hiding soft input
This may be a possible solution:
EditText et;
et.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
if(i == KeyEvent.KEYCODE_BACK) {
et.clearFocus();
return true;
}
else return false;
}
});
Related
I've created a custom view that should be a button, but i can't quite get it to work.
So my custom view is extended from View, and I need to to make a fragment navigation when clicked
I've tried to use the override fun performClick() and in it do a rootView.findNavController().navigate(R.id.action_menu_to_settings) but it crashes. I also tried use the .setOnClickLister() { // navigation } but it also doesn't work.
Can anyone tell me how set a clickListener on a custom view for a navigation? Thx
If you are creating a custom view, the best way to handle the click operations is like this:
class MyView #JvmOverloads constructor (
context: Context,
attrs: AttributeSet? = null,
defStyleRes: Int = 0,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleRes, defStyleAttr) {
var touchType = -1 // No user touch yet.
var onClickListener: () -> Unit = {
Log.d(TAG, "on click not yet implemented")
}
override fun onDraw(canvas: Canvas?) {
/* your code here */
}
override fun onTouchEvent(e: MotionEvent?): Boolean {
val value = super.onTouchEvent(e)
when(e?.action) {
MotionEvent.ACTION_DOWN -> {
/* Determine where the user has touched on the screen. */
touchType = 1 // for eg.
return true
}
MotionEvent.ACTION_UP -> {
/* Now that user has lifted his finger. . . */
when (touchType) {
1 -> onClickListener()
}
}
}
return value
}
}
And in your client class (Activity/Fragment), with the instance of the specific custom view that you instantiated, apply the following:
myView.onClickListener = {
findNavController().navigate(R.id.action_menu_to_settings)
}
I am using a MotionLayout with a scene-xml:
<Transition
motion:constraintSetStart="#+id/start"
motion:constraintSetEnd="#+id/end"
>
<OnSwipe
motion:touchAnchorId="#+id/v_top_sheet"
motion:touchRegionId="#+id/v_top_sheet_touch_region"
motion:touchAnchorSide="bottom"
motion:dragDirection="dragDown" />
</Transition>
The 2 ConstraintSets are referencing only 2 View IDs: v_notifications_container and v_top_sheet.
In my Activity I want to set a normal ClickListener to one of the other Views in this MotionLayout:
iv_notification_status.setOnClickListener { Timber.d("Hello") }
This line is executed, but the ClickListener is never triggered. I searched other posts, but most of them deal with setting a ClickListener on the same View that is the motion:touchAnchorId. This is not the case here. The ClickListener is set to a View that is not once mentioned in the MotionLayout setup. If I remove the app:layoutDescription attribute, the click works.
I also tried to use setOnTouchListener, but it is also never called.
How can I set a click listener within a MotionLayout?
With the help of this great medium article I figured out that MotionLayout is intercepting click events even though the motion scene only contains an OnSwipe transition.
So I wrote a customized MotionLayout to only handle ACTION_MOVE and pass all other touch events down the View tree. Works like a charm:
/**
* MotionLayout will intercept all touch events and take control over them.
* That means that View on top of MotionLayout (i.e. children of MotionLayout) will not
* receive touch events.
*
* If the motion scene uses only a onSwipe transition, all click events are intercepted nevertheless.
* This is why we override onInterceptTouchEvent in this class and only let swipe actions be handled
* by MotionLayout. All other actions are passed down the View tree so that possible ClickListener can
* receive the touch/click events.
*/
class ClickableMotionLayout: MotionLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
if (event?.action == MotionEvent.ACTION_MOVE) {
return super.onInterceptTouchEvent(event)
}
return false
}
}
#muetzenflo's response is the most efficient solution I've seen so far for this problem.
However, only checking the Event.Action for MotionEvent.ACTION_MOVE causes the MotionLayout to respond poorly. It is better to differentiate between movement and a single click by the use of ViewConfiguration.TapTimeout as the example below demonstrates.
public class MotionSubLayout extends MotionLayout {
private long mStartTime = 0;
public MotionSubLayout(#NonNull Context context) {
super(context);
}
public MotionSubLayout(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public MotionSubLayout(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if ( event.getAction() == MotionEvent.ACTION_DOWN ) {
mStartTime = event.getEventTime();
} else if ( event.getAction() == MotionEvent.ACTION_UP ) {
if ( event.getEventTime() - mStartTime <= ViewConfiguration.getTapTimeout() ) {
return false;
}
}
return super.onInterceptTouchEvent(event);
}
}
small modification of #TimonNetherlands code that works on the pixel4 aswell
class ClickableMotionLayout: MotionLayout {
private var mStartTime: Long = 0
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
if ( event?.action == MotionEvent.ACTION_DOWN ) {
mStartTime = event.eventTime;
}
if ((event?.eventTime?.minus(mStartTime)!! >= ViewConfiguration.getTapTimeout()) && event.action == MotionEvent.ACTION_MOVE) {
return super.onInterceptTouchEvent(event)
}
return false;
}
}
#Finn Marquardt's solution is efficient, but making a check only on ViewConfiguration.getTapTimeout() is not 100% reliable in my opinion and for me, sometimes the click event won't trigger because the duration of the tap is greater than getTapTimeout() (which is only 100ms). Also Long press is not handled.
Here is my solution, using GestureDetector:
class ClickableMotionLayout : MotionLayout {
private var isLongPressing = false
private var compatGestureDetector : GestureDetectorCompat? = null
var gestureListener : GestureDetector.SimpleOnGestureListener? = null
init {
setupGestureListener()
setOnTouchListener { v, event ->
if(isLongPressing && event.action == MotionEvent.ACTION_UP){
isPressed = false
isLongPressing = false
v.performClick()
} else {
isPressed = false
isLongPressing = false
compatGestureDetector?.onTouchEvent(event) ?: false
}
}
}
Where setupGestureListener() is implemented like this:
fun setupGestureListener(){
gestureListener = object : GestureDetector.SimpleOnGestureListener(){
override fun onLongPress(e: MotionEvent?) {
isPressed = progress == 0f
isLongPressing = progress == 0f
}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
isPressed = true
performClick()
return true
}
}
compatGestureDetector = GestureDetectorCompat(context, gestureListener)
}
The GestureDetector handles the touch event only if it's a tap or if it's a long press (and it will manually trigger the "pressed" state). Once the user lifts the finger and the touch event is actually a long press, then a click event is triggered. In any other cases, MotionLayout will handle the event.
I'm afraid none of the other answers worked for me, I don't know it's because of an update in the libarary, but I don't get an ACTION_MOVE event on the region set in onSwipe.
Instead, this is what worked for me in the end:
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.children
/**
* This MotionLayout allows handling of clicks that clash with onSwipe areas.
*/
class ClickableMotionLayout #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : MotionLayout(context, attrs, defStyleAttr) {
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
// Take all child views that are clickable,
// then see if any of those have just been clicked, and intercept the touch.
// Otherwise, let the MotionLayout handle the touch event.
if (children.filter { it.isClickable }.any {
it.x < event.x && it.x + it.width > event.x &&
it.y < event.y && it.y + it.height > event.y
}) {
return false
}
return super.onInterceptTouchEvent(event)
}
}
Basically, when we get a touch event, we iterate over all children of the MotionLayout and see if any of them (that are clickable) were the target of the event. If so, we intercept the touch event, and otherwise we let the MotionLayout do its thing.
This allows the user to click on clickable views that clashes with the onSwipe area, while also allowing swiping even if the swipe starts on the clickable view.
To setup onClick action on the view, use:
android:onClick="handleAction"
inside the MotionLayout file, and define "handleAction" in your class.
Is there any way to specify the input method type for android.support.v7.preference.EditTextPreference?
If you don't want to use a third party library, it is possible to specify a layout to the EditTextPreference
<EditTextPreference android:defaultValue="0"
android:key="some_key"
android:title="Title"
android:dialogLayout="#layout/preference_edit_text"/>
Then in res/layout/preference_edit_text.xml
<android.support.constraint.ConstraintLayout
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="wrap_content">
<EditText android:id="#android:id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:singleLine="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="21dp"
android:layout_marginEnd="21dp"/>
</android.support.constraint.ConstraintLayout>
Please note that the edit text id must be : #android:id/edit
but then you are free to use whatever you want inside the android:inputType field
I'm sure there's a better way to align the EditText rather than using 21dp margins
but at least it works
Now one can use Android-Support-Preference-V7-Fix library.Fixed EditTextPreference forwards the XML attributes (like inputType) to the EditText, just like the original preference did.
Here is my version of the answer from Cory Charlton, transfered to Jetpack preferences and written in Kotlin:
import android.content.Context
import android.content.SharedPreferences
import android.text.InputType
import android.util.AttributeSet
import androidx.preference.EditTextPreference
class EditIntegerPreference : EditTextPreference {
constructor(context: Context?) : super(context) {
setInputMethod()
}
constructor(context: Context?, attributeSet: AttributeSet?) : super(context, attributeSet) {
setInputMethod()
}
constructor(context: Context?, attributeSet: AttributeSet?, defStyle: Int) : super(
context,
attributeSet,
defStyle
) {
setInputMethod()
}
override fun getText(): String =
try {
java.lang.String.valueOf(sharedPreferences.getInt(key, 0))
} catch (e: Exception) {
"0"
}
override fun setText(text: String?) {
try {
if (text != null) {
sharedPreferences?.edit()?.putInt(key, text.toInt())?.apply()
summary = text
} else {
sharedPreferences?.remove(key)
summary = ""
}
} catch (e: Exception) {
sharedPreferences?.remove(key)
summary = ""
}
}
override fun onSetInitialValue(defaultValue: Any?) {
val defaultValueInt: Int =
when (defaultValue){
is Int -> defaultValue
is String -> try {defaultValue.toInt()} catch (ex: java.lang.Exception){0}
else -> 0
}
text = sharedPreferences.getInt(key, defaultValueInt).toString()
}
private fun setInputMethod() {
setOnBindEditTextListener {
it.inputType = InputType.TYPE_CLASS_NUMBER
}
}
fun SharedPreferences.remove(key: String) = edit().remove(key).apply()
}
Edit: The previous answers below were built on the stock android.preference.EditTextPreference and unfortunately don't work for the android.support.v7.preference.EditTextPreference.
In the android.preference.EditTextPreference the EditText control is created programmatically and the AttributeSet from the Preference is passed to it.
android.preference.EditTextPreference Source:
public EditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mEditText = new EditText(context, attrs);
// Give it an ID so it can be saved/restored
mEditText.setId(com.android.internal.R.id.edit);
/*
* The preference framework and view framework both have an 'enabled'
* attribute. Most likely, the 'enabled' specified in this XML is for
* the preference framework, but it was also given to the view framework.
* We reset the enabled state.
*/
mEditText.setEnabled(true);
}
White allows us to set the inputType on the Preference itself and have it pass through to the EditText. Unfortunately the android.support.v7.preference.EditTextPreference appears to create the EditText in the Layout
See this issue for ideas on working around this:
Just wanted to let you know that subclassing EditTextPreferenceDialogFragment and overriding onAddEditTextToDialogView as well as overriding PreferenceFragmentCompat#onDisplayPreferenceDialog to show that subclass as needed seems to be working fine, thanks for the help.
Create your own class that extends the EditTextPreference and set it there.
Here's my EditIntegerPreference class:
public class EditIntegerPreference extends EditTextPreference {
public EditIntegerPreference(Context context) {
super(context);
}
public EditIntegerPreference(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
public EditIntegerPreference(Context context, AttributeSet attributeSet, int defStyle) {
super(context, attributeSet, defStyle);
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
getEditText().setSelectAllOnFocus(true);
}
#Override
public String getText() {
try {
return String.valueOf(getSharedPreferences().getInt(getKey(), 0));
} catch (Exception e) {
return getSharedPreferences().getString(getKey(), "0");
}
}
#Override
public void setText(String text) {
try {
if (getSharedPreferences() != null) {
getSharedPreferences().edit().putInt(getKey(), Integer.parseInt(text)).commit();
}
} catch (Exception e) {
// TODO: This catch stinks!
}
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
getEditText().setSelectAllOnFocus(true);
if (restoreValue) {
getEditText().setText(getText());
} else {
super.onSetInitialValue(restoreValue, defaultValue != null ? defaultValue : "");
}
}
}
Note that it is possible to add the inputType attribute to the the EditTextPreference
android:inputType="number"
The reason I didn't go this route is that I wanted my preference to get stored as an Integer and not a String
Workaround for Kotlin + DataStore + androidx.Preference
Disclaimer!
Probably this isn't the way to do it!
Ideally one should only:
set the input to int
override in a DataStore class: putInt/getInt
Extension Function
In my case I had a PreferenceFragmentCompat, so:
fun PreferenceFragmentCompat.setNumericInput(
#StringRes prefRes: Int, initialValue: String) {
val preference = findPreference(getString(prefRes)) as EditTextPreference?
preference?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER or
InputType.TYPE_NUMBER_FLAG_SIGNED
// set the initial value: I read it from the DataStore and then
// pass it as the 2nd argument to setNumericInput.
// BTW, I do store stringPreferenceKeys, as it's the putString method
// that get's triggered
if (editText.text.isEmpty()) editText.setText(initialValue)
editText.setSelection(editText.text.length) // put cursor at the end
}
// to use it in the summary do something like:
preference?.setOnPreferenceChangeListener { it, newValue ->
it.summary = "updated: $newValue"
true
}
}
Also, in my Activity that extends BaseSettingsActivity I
replace the management from SharedPreferences using:
preferenceManager.preferenceDataStore = dataStoreCvLogger
Is there anyway to know when a progressbar has reached it maximum. Like a Listener then could plug into the ProgressBar and lets us know that the progress bar is at the maximum level ?
Kind Regards
There isn't a direct way to do this. A workaround could be to make a custom implementation of the ProgressBar and override the setProgress method:
public MyProgressBar extends ProgressBar
{
#Override
public void setProgress(int progress)
{
super.setProgress(progress);
if(progress == this.getMax())
{
//Do stuff when progress is max
}
}
}
I think the cleanest way would be just adding this to your code:
if (progressBar.getProgress() == progressBar.getMax()) {
// do stuff you need
}
If you need onProgressChanged like SeekBar - create custom progress (Kotlin):
class MyProgressBar : ProgressBar {
private var listener: OnMyProgressBarChangeListener? = null
fun setOnMyProgressBarChangeListener(l: OnMyProgressBarChangeListener) {
listener = l
}
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(
context,
attrs
)
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int
) : super(context, attrs, defStyleAttr)
override fun setProgress(progress: Int) {
super.setProgress(progress)
listener?.onProgressChanged(this)
}
interface OnMyProgressBarChangeListener {
fun onProgressChanged(myProgressBar: MyProgressBar?)
}
}
And for example in your fragment:
progress_bar?.setOnMyProgressBarChangeListener(object :
MyProgressBar.OnMyProgressBarChangeListener {
override fun onProgressChanged(myProgressBar: MyProgressBar?) {
val p = progress_bar.progress
// do stuff like this
if (p != 100) {
percentCallback?.changePercent(p.toString()) // show animation custom view with grow percents
} else {
shieldView.setFinishAndDrawCheckMark() // draw check mark
}
}
})
I think overall you should never have to do this. Is there just one valid case where you need to listen to a progressbar progress? I mean, usually it's the other way around: you set the progressbar progress based on something, not the other way around, so you need to track the progress of that something instead of listening to a view (which may or may not exist by the way).
With the Android SeekBar, you can normally drag the thumb to the left or to the right and the yellow progress color is to the left of the thumb. I want the exact opposite, essentially flipping the yellow progress color to the right of the thumb and flipping the entire SeekBar on the y-axis.
Can anyone point me in the right direction? Thanks!
After fiddling around with some code this is what I got and it seems to work pretty well. Hopefully it will help someone else in the future.
public class ReversedSeekBar extends SeekBar {
public ReversedSeekBar(Context context) {
super(context);
}
public ReversedSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReversedSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onDraw(Canvas canvas) {
float px = this.getWidth() / 2.0f;
float py = this.getHeight() / 2.0f;
canvas.scale(-1, 1, px, py);
super.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
event.setLocation(this.getWidth() - event.getX(), event.getY());
return super.onTouchEvent(event);
}
}
This was thrown together with the help of these two questions:
How can you display upside down text with a textview in Android?
How can I get a working vertical SeekBar in Android?
Have you tried seekbar.setRotation( 180 )? It flips the seekbar 180 degrees and is upside down, meaning left side is max, right side is 0 with the color on the right of the thumb. No need to create a custom seekbar this way.
You should look into making a custom progress bar. Considering what you want to do, you already have the images you need in the Android SDK. I'd extract them and edit them accordingly. Here's a tutorial to help get you started.
Have you tried setting this in xml
android:rotationY="180"
This should fix a few issues with #mhenry answer
class ReverseSeekBar : SeekBar {
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {
init()
}
var first = true
override fun onTouchEvent(event: MotionEvent): Boolean {
event.setLocation(this.width - event.x, event.y)
return super.onTouchEvent(event)
}
override fun getProgress(): Int {
return max - super.getProgress() + min
}
override fun setProgress(progress: Int) {
super.setProgress(max - progress + min)
}
override fun onDraw(canvas: Canvas?) {
if (first) {
first = false
val old = progress
progress = min + max - progress
super.onDraw(canvas)
progress = old
} else
super.onDraw(canvas)
}
private fun init() {
rotation = 180f
}
}