Fix desc icon clickable area (#2025)

* Fix desc icon clickable area

* Added java docs
This commit is contained in:
Vivek Maskara 2018-11-25 23:38:15 +05:30 committed by Josephine Lim
parent d62a7586e0
commit a248e6114c
4 changed files with 286 additions and 97 deletions

View file

@ -0,0 +1,204 @@
package fr.free.nrw.commons.ui.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.EditText;
/**
* Custom edit text with a drawable click listener
* https://stackoverflow.com/questions/13135447/setting-onclicklistener-for-the-drawable-right-of-an-edittext
*/
@SuppressLint("AppCompatCustomView")
public class CustomEditText extends EditText {
private Drawable drawableRight;
private Drawable drawableLeft;
private Drawable drawableTop;
private Drawable drawableBottom;
int actionX, actionY;
private DrawableClickListener clickListener;
public CustomEditText(Context context, AttributeSet attrs) {
super(context, attrs);
// this Contructure required when you are using this view in xml
}
public CustomEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
public void setCompoundDrawables(Drawable left, Drawable top,
Drawable right, Drawable bottom) {
if (left != null) {
drawableLeft = left;
}
if (right != null) {
drawableRight = right;
}
if (top != null) {
drawableTop = top;
}
if (bottom != null) {
drawableBottom = bottom;
}
super.setCompoundDrawables(left, top, right, bottom);
}
/**
* Fires the appropriate drawable click listener on touching the icon
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
Rect bounds;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
actionX = (int) event.getX();
actionY = (int) event.getY();
if (drawableBottom != null
&& drawableBottom.getBounds().contains(actionX, actionY)) {
clickListener.onClick(DrawableClickListener.DrawablePosition.BOTTOM);
return super.onTouchEvent(event);
}
if (drawableTop != null
&& drawableTop.getBounds().contains(actionX, actionY)) {
clickListener.onClick(DrawableClickListener.DrawablePosition.TOP);
return super.onTouchEvent(event);
}
// this works for left since container shares 0,0 origin with bounds
if (drawableLeft != null) {
bounds = null;
bounds = drawableLeft.getBounds();
int x, y;
int extraTapArea = (int) (13 * getResources().getDisplayMetrics().density + 0.5);
x = actionX;
y = actionY;
if (!bounds.contains(actionX, actionY)) {
/** Gives the +20 area for tapping. */
x = (int) (actionX - extraTapArea);
y = (int) (actionY - extraTapArea);
if (x <= 0)
x = actionX;
if (y <= 0)
y = actionY;
/** Creates square from the smallest value */
if (x < y) {
y = x;
}
}
if (bounds.contains(x, y) && clickListener != null) {
clickListener
.onClick(DrawableClickListener.DrawablePosition.LEFT);
event.setAction(MotionEvent.ACTION_CANCEL);
return false;
}
}
if (drawableRight != null) {
bounds = null;
bounds = drawableRight.getBounds();
int x, y;
int extraTapArea = 13;
/**
* IF USER CLICKS JUST OUT SIDE THE RECTANGLE OF THE DRAWABLE
* THAN ADD X AND SUBTRACT THE Y WITH SOME VALUE SO THAT AFTER
* CALCULATING X AND Y CO-ORDINATE LIES INTO THE DRAWBABLE
* BOUND. - this process help to increase the tappable area of
* the rectangle.
*/
x = (int) (actionX + extraTapArea);
y = (int) (actionY - extraTapArea);
/**Since this is right drawable subtract the value of x from the width
* of view. so that width - tappedarea will result in x co-ordinate in drawable bound.
*/
x = getWidth() - x;
/*x can be negative if user taps at x co-ordinate just near the width.
* e.g views width = 300 and user taps 290. Then as per previous calculation
* 290 + 13 = 303. So subtract X from getWidth() will result in negative value.
* So to avoid this add the value previous added when x goes negative.
*/
if (x <= 0) {
x += extraTapArea;
}
/* If result after calculating for extra tappable area is negative.
* assign the original value so that after subtracting
* extratapping area value doesn't go into negative value.
*/
if (y <= 0)
y = actionY;
/**If drawble bounds contains the x and y points then move ahead.*/
if (bounds.contains(x, y) && clickListener != null) {
clickListener
.onClick(DrawableClickListener.DrawablePosition.RIGHT);
event.setAction(MotionEvent.ACTION_CANCEL);
return false;
}
return super.onTouchEvent(event);
}
}
return super.onTouchEvent(event);
}
@Override
protected void finalize() throws Throwable {
drawableRight = null;
drawableBottom = null;
drawableLeft = null;
drawableTop = null;
super.finalize();
}
/**
* Attaches the drawable click listener to the custom edit text
* @param listener
*/
public void setDrawableClickListener(DrawableClickListener listener) {
this.clickListener = listener;
}
/**
* Interface for drawable click listener
*/
public interface DrawableClickListener {
enum DrawablePosition {TOP, BOTTOM, LEFT, RIGHT}
void onClick(DrawablePosition target);
}
}

View file

@ -4,26 +4,22 @@ import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.AppCompatSpinner;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.EditText;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnTouch;
import butterknife.Optional;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.ui.widget.CustomEditText;
import fr.free.nrw.commons.utils.AbstractTextWatcher;
import fr.free.nrw.commons.utils.BiMap;
import fr.free.nrw.commons.utils.ViewUtil;
@ -31,8 +27,6 @@ import io.reactivex.subjects.BehaviorSubject;
import io.reactivex.subjects.Subject;
import timber.log.Timber;
import static android.view.MotionEvent.ACTION_UP;
class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewHolder> {
private Title title;
@ -119,7 +113,7 @@ class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewH
AppCompatSpinner spinnerDescriptionLanguages;
@BindView(R.id.description_item_edit_text)
EditText descItemEditText;
CustomEditText descItemEditText;
private View view;
@ -154,6 +148,18 @@ class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewH
}
});
descItemEditText.setDrawableClickListener(target -> {
switch (target) {
case RIGHT:
if (getAdapterPosition() == 0) {
callback.showAlert(R.string.media_detail_title, R.string.title_info);
}
break;
default:
break;
}
});
} else {
Description description = descriptions.get(position - 1);
Timber.d("Description is " + description);
@ -168,9 +174,7 @@ class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewH
descItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
}
descItemEditText.addTextChangedListener(new AbstractTextWatcher(descriptionText -> {
description.setDescriptionText(descriptionText);
}));
descItemEditText.addTextChangedListener(new AbstractTextWatcher(description::setDescriptionText));
descItemEditText.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
ViewUtil.hideKeyboard(v);
@ -179,95 +183,75 @@ class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewH
}
});
SpinnerLanguagesAdapter languagesAdapter = new SpinnerLanguagesAdapter(context,
R.layout.row_item_languages_spinner, selectedLanguages);
languagesAdapter.notifyDataSetChanged();
spinnerDescriptionLanguages.setAdapter(languagesAdapter);
if (description.getSelectedLanguageIndex() == -1) {
if (position == 1) {
int defaultLocaleIndex = languagesAdapter.getIndexOfUserDefaultLocale(context);
spinnerDescriptionLanguages.setSelection(defaultLocaleIndex);
} else {
spinnerDescriptionLanguages.setSelection(0);
}
} else {
spinnerDescriptionLanguages.setSelection(description.getSelectedLanguageIndex());
selectedLanguages.put(spinnerDescriptionLanguages, description.getLanguageCode());
}
//TODO do it the butterknife way
spinnerDescriptionLanguages.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position,
long l) {
description.setSelectedLanguageIndex(position);
String languageCode = ((SpinnerLanguagesAdapter) adapterView.getAdapter()).getLanguageCode(position);
description.setLanguageCode(languageCode);
selectedLanguages.remove(adapterView);
selectedLanguages.put(adapterView, languageCode);
((SpinnerLanguagesAdapter) adapterView.getAdapter()).selectedLangCode = languageCode;
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
descItemEditText.setDrawableClickListener(target -> {
switch (target) {
case RIGHT:
if (getAdapterPosition() == 1) {
callback.showAlert(R.string.media_detail_description,
R.string.description_info);
}
break;
default:
break;
}
});
initLanguageSpinner(position, description);
}
}
@Optional
@OnTouch(R.id.description_item_edit_text)
boolean descriptionInfo(View view, MotionEvent motionEvent) {
//Title info is visible only for the title
if (getAdapterPosition() == 0) {
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
final int value = view.getRight() - descItemEditText
.getCompoundDrawables()[2]
.getBounds().width();
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
callback.showAlert(R.string.media_detail_title, R.string.title_info);
return true;
}
} else {
final int value = descItemEditText.getLeft() + descItemEditText
.getCompoundDrawables()[0]
.getBounds().width();
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() <= value) {
callback.showAlert(R.string.media_detail_title, R.string.title_info);
return true;
}
}
//Description info is visible only for the first description
} else if (getAdapterPosition() == 1) {
final int value;
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
value = view.getRight() - descItemEditText.getCompoundDrawables()[2].getBounds().width();
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
callback.showAlert(R.string.media_detail_description,
R.string.description_info);
return true;
}
} else {
value = descItemEditText.getLeft() + descItemEditText
.getCompoundDrawables()[0]
.getBounds().width();
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() <= value) {
callback.showAlert(R.string.media_detail_description,
R.string.description_info);
return true;
}
}
}
return false;
}
}
/**
* Extracted out the function to init the language spinner with different system supported languages
* @param position
* @param description
*/
private void initLanguageSpinner(int position, Description description) {
SpinnerLanguagesAdapter languagesAdapter = new SpinnerLanguagesAdapter(context,
R.layout.row_item_languages_spinner, selectedLanguages);
languagesAdapter.notifyDataSetChanged();
spinnerDescriptionLanguages.setAdapter(languagesAdapter);
private Drawable getInfoIcon() {
return context.getResources()
.getDrawable(R.drawable.mapbox_info_icon_default);
if (description.getSelectedLanguageIndex() == -1) {
if (position == 1) {
int defaultLocaleIndex = languagesAdapter.getIndexOfUserDefaultLocale(context);
spinnerDescriptionLanguages.setSelection(defaultLocaleIndex);
} else {
spinnerDescriptionLanguages.setSelection(0);
}
} else {
spinnerDescriptionLanguages.setSelection(description.getSelectedLanguageIndex());
selectedLanguages.put(spinnerDescriptionLanguages, description.getLanguageCode());
}
//TODO do it the butterknife way
spinnerDescriptionLanguages.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position,
long l) {
description.setSelectedLanguageIndex(position);
String languageCode = ((SpinnerLanguagesAdapter) adapterView.getAdapter()).getLanguageCode(position);
description.setLanguageCode(languageCode);
selectedLanguages.remove(adapterView);
selectedLanguages.put(adapterView, languageCode);
((SpinnerLanguagesAdapter) adapterView.getAdapter()).selectedLangCode = languageCode;
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
}
/**
* Extracted out the method to get the icon drawable
* @return
*/
private Drawable getInfoIcon() {
return context.getResources().getDrawable(R.drawable.mapbox_info_icon_default);
}
}
public interface Callback {

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
@ -20,14 +19,16 @@
android:layout_height="wrap_content"
android:layout_weight="8">
<EditText
<fr.free.nrw.commons.ui.widget.CustomEditText
android:id="@+id/description_item_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/mapbox_info_icon_default"
android:drawableRight="@drawable/mapbox_info_icon_default"
android:hint="@string/share_description_hint"
android:imeOptions="flagNoExtractUi"
android:drawablePadding="4dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:drawableRight="@drawable/mapbox_info_icon_default"
android:inputType="textMultiLine" />
</android.support.design.widget.TextInputLayout>

View file

@ -7,7 +7,7 @@
android:layout_height="wrap_content"
tools:showIn="@layout/activity_upload">
<android.support.design.widget.TextInputEditText
<fr.free.nrw.commons.ui.widget.CustomEditText
android:id="@+id/description_item_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"