I use GestureDetector to implement scrolling inside a custom View. My implementation is based on this: Smooth scrolling with inertia and edge resistance/snapback
I noticed a short pause before the scrolling starts: I examined the onScroll messages and noticed that the first one arrives only after a larger movement of a finger, which causes noticable lag at the beginning of the scrolling. After that the scrolling is smooth.
It seems GestureDetector starts sending onScroll messages only after a minimal distance between the motionevents to make sure the gesture is not a longtap or tap (btw I set setIsLongpressEnabled(false)).
Is there any way to change this behaviour and create a smooth scroll without implementing a custom scroll gesture using low level touch events?
The answer is no, you have to create your own GestureDetector. If you look at the Android source code (GestureDetector.java) lines 524 to 540 are use to detect the "touch slop" for a single tap. Specifically line 528 prevents the onScroll event from being called until the movement is outside the touch slop (which is pulled from the view configuration). You cannot change the view configuration and the slop is hard coded at 16 pixels. This is the radius that causes the lag that you are seeing.
You can use reflection to change mTouchSlopSquare from GestureDetector.java
public static void setGestureDetectorTouchSlop(GestureDetector gestureDetector, int value) {
try {
Field f_mTouchSlopSquare = GestureDetector.class.getDeclaredField("mTouchSlopSquare");
f_mTouchSlopSquare.setAccessible(true);
f_mTouchSlopSquare.setInt(gestureDetector, value * value);
} catch (NoSuchFieldException | IllegalAccessException | NullPointerException e) {
Log.w(TAG, gestureDetector.toString(), e);
}
}
Also, here is the method to change the slop for the GestureDetectorCompat.java
public static void setGestureDetectorTouchSlop(GestureDetectorCompat gestureDetector, int value) {
try {
Field f_mImpl = GestureDetectorCompat.class.getDeclaredField("mImpl");
f_mImpl.setAccessible(true);
Object mImpl = f_mImpl.get(gestureDetector);
if (mImpl == null) {
Log.w(TAG, f_mImpl + " is null");
return;
}
Class<?> c_GDCIJellybeanMr2 = null;
Class<?> c_GDCIBase = null;
try {
c_GDCIJellybeanMr2 = Class.forName(GestureDetectorCompat.class.getName() + "$GestureDetectorCompatImplJellybeanMr2");
c_GDCIBase = Class.forName(GestureDetectorCompat.class.getName() + "$GestureDetectorCompatImplBase");
} catch (ClassNotFoundException ignored) {
}
if (c_GDCIJellybeanMr2 != null && c_GDCIJellybeanMr2.isInstance(mImpl)) {
Field f_mDetector = c_GDCIJellybeanMr2.getDeclaredField("mDetector");
f_mDetector.setAccessible(true);
Object mDetector = f_mDetector.get(mImpl);
if (mDetector instanceof GestureDetector)
setGestureDetectorTouchSlop((GestureDetector) mDetector, value);
} else if (c_GDCIBase != null) {
Field f_mTouchSlopSquare = c_GDCIBase.getDeclaredField("mTouchSlopSquare");
f_mTouchSlopSquare.setAccessible(true);
f_mTouchSlopSquare.setInt(mImpl, value * value);
} else {
Log.w(TAG, "not handled: " + mImpl.getClass().toString());
}
} catch (NoSuchFieldException | IllegalAccessException | NullPointerException e) {
Log.w(TAG, gestureDetector.getClass().toString(), e);
}
}
Related
I have a memory leak because of AppCompatTextView
It has no click listeners it's just a plain TexView with some text in it.
Is there anything I can do about that? Is that a bug or am I doing something wrong?
I've tried solution suggested here but that didn't helped.
It's an android framework bug. https://code.google.com/p/android/issues/detail?id=34731
It hasn't been fixed yet, even in support library.
Here is the fix:
public static void fixInputMethodManagerLeak(Context destContext) {
if (destContext == null) {
return;
}
InputMethodManager imm = (InputMethodManager) destContext.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) {
return;
}
String[] arr = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
Field f = null;
Object obj_get = null;
for (int i = 0; i < arr.length; i++) {
String param = arr[i];
try {
f = imm.getClass().getDeclaredField(param);
if (!f.isAccessible()) {
f.setAccessible(true);
}
obj_get = f.get(imm);
if (obj_get != null && obj_get instanceof View) {
View v_get = (View) obj_get;
if (v_get.getContext() == destContext) { // referenced context is held InputMethodManager want to destroy targets
f.set(imm, null); // set empty, destroyed node path to gc
} else {
// Not want to destroy the target, that is, again into another interface, do not deal with, to avoid affecting the original logic, there is nothing further for the cycle
Log.e(TAG, "fixInputMethodManagerLeak break, context is not suitable, get_context=" + v_get.getContext() + " dest_context=" + destContext);
break;
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Call it like this:
#Override
protected void onDestroy() {
super.onDestroy();
//if you get memory leak on configuration change too, remove the if clause.
if (isFinishing()) {
fixInputMethodManagerLeak(this);
}
}
Take a look at this question too.
According to this link:
https://code.google.com/p/android/issues/detail?id=179272
It seems that the leak is caused by:
It happens with anything which uses TextLine (TextView, descendants, Layout) with Spanned text. As SearchView uses a SpannableStringBuilder internally, it gets leaked.
I hope it will help you :)
If you call AbsListView.setItemChecked() directly, it works well, and the ActionMode will activate and create.
mGridView.setItemChecked(pPosition, true);
But when you call View.startActionMode() first, then call AbsListView.setItemChecked(), the ActionMode create by startActionMode() will destroy, and recreate a new one by setItemChecked().
My question is: How to avoid this issue when call View.startActionMode() first?
Looking forward to your reply! Thanks!
Why recreate a new one? See the source code of AbsListView.setItemChecked(int position, boolean value) method, you can see following code:
// Start selection mode if needed. We don't need to if we're unchecking something.
if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
if (mMultiChoiceModeCallback == null ||
!mMultiChoiceModeCallback.hasWrappedCallback()) {
throw new IllegalStateException("AbsListView: attempted to start selection mode " +
"for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
"supplied. Call setMultiChoiceModeListener to set a callback.");
}
mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
}
That means if mChoiceActionMode == null, it will call startActionMode(mMultiChoiceModeCallback), so will recreate a new ActionMode.
And how to fix?
Here is a simple way: use reflect to assign a ActionMode create by startActionMode() to the private field mChoiceActionMode in AbsListView.
private void startActionMode() {
// Get the field "mMultiChoiceModeCallback" instance by reflect
AbsListView.MultiChoiceModeListener wrapperIns = null;
try {
Field wrapper = null;
wrapper = AbsListView.class.getDeclaredField("mMultiChoiceModeCallback");
wrapper.setAccessible(true);
wrapperIns = (AbsListView.MultiChoiceModeListener) wrapper.get(mMessageGridView);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
// Start the ActionMode, but not select any item.
ActionMode actionMode = mMessageGridView.startActionMode(wrapperIns);
// Assign actionMode to field "mChoiceActionMode" by reflect
try {
Field mChoiceActionMode = null;
mChoiceActionMode = AbsListView.class.getDeclaredField("mChoiceActionMode");
mChoiceActionMode.setAccessible(true);
mChoiceActionMode.set(mMessageGridView, actionMode);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
Why here we use wrapper? Because AbsListView.setMultiChoiceModeListener(MultiChoiceModeListener listener) will wrap our mMultiChoiceModeListener, so we can't not use directly.
I am developing a block game. i have two moving blocks in vertical direction. one of the block is also moving horizontally (like auto moving car in ROAD FIGHTER). i am checking the block interaction by Intersector.overlaps but when my block goes out of window (i.e. position becomes less than 0) ,my code of overlaps not working .
if(Intersector.overlaps(block1.getBoundingRectangle(), block2.getBoundingRectangle())){
// do not update values
}else{
// update the values of block1 and block 2
}
public static int[] value={138,195,248,303};
public static boolean[] valueHolder={true,false,false,true};
public void update(float delta) {
velocity.set(0, backgroun.velocity.y-scrollSpeed);
position.add(velocity.cpy().scl(delta));
if(position.y>850){
isScrollableTop=true;
}
selfMove(delta);
if(position.y<-150){
position.y=-150;
}
carBoundingRectangle.setX(position.x);
carBoundingRectangle.setY(position.y);
}
private void changeArray() {
if(position.x>=138 && position.x<167){
if(!GameConstants.valueHolder[0]){
GameConstants.valueHolder[blockNumber]=false;
GameConstants.valueHolder[0]=true;
blockNumber=0;
}
}else if(position.x>=167 && position.x<222 ){
if(!GameConstants.valueHolder[1]){
GameConstants.valueHolder[blockNumber]=false;
GameConstants.valueHolder[1]=true;
blockNumber=1;
}
}else if(position.x>=222 && position.x<275){
if(!GameConstants.valueHolder[2]){
GameConstants.valueHolder[blockNumber]=false;
GameConstants.valueHolder[2]=true;
blockNumber=2;
}
}else if(position.x>=275 ){
if(!GameConstants.valueHolder[3]){
GameConstants.valueHolder[blockNumber]=false;
GameConstants.valueHolder[3]=true;
blockNumber=3;
}
}
}
Thanks in advance
I have a NumberPicker that has a formatter that formats the displayed numbers either when the NumberPicker spins or when a value is entered manually. This works fine, but when the NumberPicker is first shown and I initialize it with setValue(0) the 0 does not get formatted (it should display as "-" instead of 0). As soon as I spin the NumberPicker from that point on everything works.
How can I force the NumberPicker to format always - Both on first rendering and also when I enter a number manually with the keyboard?
This is my formatter
public class PickerFormatter implements Formatter {
private String mSingle;
private String mMultiple;
public PickerFormatter(String single, String multiple) {
mSingle = single;
mMultiple = multiple;
}
#Override
public String format(int num) {
if (num == 0) {
return "-";
}
if (num == 1) {
return num + " " + mSingle;
}
return num + " " + mMultiple;
}
}
I add my formatter to the picker with setFormatter(), this is all I do to the picker.
picker.setMaxValue(max);
picker.setMinValue(min);
picker.setFormatter(new PickerFormatter(single, multiple));
picker.setWrapSelectorWheel(wrap);
dgel's solution doesn't work for me: when I tap on the picker, formatting disappears again. This bug is caused by input filter set on EditText inside NumberPicker when setDisplayValues isn't used. So I came up with this workaround:
Field f = NumberPicker.class.getDeclaredField("mInputText");
f.setAccessible(true);
EditText inputText = (EditText)f.get(mPicker);
inputText.setFilters(new InputFilter[0]);
I also encountered this annoying little bug. Used a technique from this answer to come up with a nasty but effective fix.
NumberPicker picker = (NumberPicker)view.findViewById(id.picker);
picker.setMinValue(1);
picker.setMaxValue(5);
picker.setWrapSelectorWheel(false);
picker.setFormatter(new NumberPicker.Formatter() {
#Override
public String format(int value) {
return my_formatter(value);
}
});
try {
Method method = picker.getClass().getDeclaredMethod("changeValueByOne", boolean.class);
method.setAccessible(true);
method.invoke(picker, true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
Calling that private changeValueByOne method immediately after instantiating my number picker seems to kick the formatter enough to behave how it should. The number picker comes up nice and clean with the first value formatted correctly. Like I said, nasty but effective.
I had the same problem and I used the setDisplayedValues() method instead.
int max = 99;
String[] values = new String[99];
values[0] = “-” + mSingle
values[1] =
for(int i=2; i<=max; i++){
makeNames[i] = String.valueOf(i) + mMultiple;
}
picker.setMinValue(0);
picker.setMaxValue(max);
picker.setDisplayedValues(values)
This doesn't allow the user to set the value manually in the picker though.
The following solution worked out for me for APIs 18-26 without using reflection, and without using setDisplayedValues().
It consists of two steps:
Make sure the first element shows by setting it's visibility to invisible (I used Layout Inspector to see the difference with when it shows, it's not logical but View.INVISIBLE actually makes the view visible).
private void initNumberPicker() {
// Inflate or create your BugFixNumberPicker class
// Do your initialization on bugFixNumberPicker...
bugFixNumberPicker.setFormatter(new NumberPicker.Formatter() {
#Override
public String format(final int value) {
// Format to your needs
return aFormatMethod(value);
}
});
// Fix for bug in Android Picker where the first element is not shown
View firstItem = bugFixNumberPicker.getChildAt(0);
if (firstItem != null) {
firstItem.setVisibility(View.INVISIBLE);
}
}
Subclass NumberPicker and make sure no click events go through so the glitch where picker elements disapear on touch can't happen.
public class BugFixNumberPicker extends NumberPicker {
public BugFixNumberPicker(Context context) {
super(context);
}
public BugFixNumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BugFixNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public boolean performClick() {
return false;
}
#Override
public boolean performLongClick() {
return false;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return false;
}
}
Here's my solution based on answers by torvin and Sebastian. You don't have to subclass anything or use reflection.
View editView = numberPicker.getChildAt(0);
if (editView instanceof EditText) {
// Remove default input filter
((EditText) editView).setFilters(new InputFilter[0]);
}
Calling the private method changeValueByOne() via reflection as described in an earlier answer works for me on API Level 16 (Android 4.1.2 and up), but it does not seem to help on API Level 15 (Android 4.0.3), however!
What works for me on API Level 15 (and up) is to use your own custom formatter to create String array and pass that with the method setDisplayedValues() to the number picker.
See also: Android 3.x and 4.x NumberPicker Example
The answer provided by NoActivity worked for me but I only had to do:
View firstItem = bugFixNumberPicker.getChildAt(0);
if (firstItem != null) {
firstItem.setVisibility(View.INVISIBLE);
}
to fix the issue. I did not need to subclass NumberPicker. I did not see the issue where picker elements disappear on touch.
Kotlin version based on Nikolai's answer
private fun initNumberPicker() {
nrPicker.children.iterator().forEach {
if (it is EditText) it.filters = arrayOfNulls(0) // remove default input filter
}
}
I managed to fix it by calling
picker.invalidate();
just after setting the formatter.
Improved Nikolai answer if selected index is not 0. Not to great for performances but fix the problem..
for(index in numberPicker.minValue..numberPicker.maxValue) {
val editView = numberPicker.getChildAt(index-numberPicker.minValue)
if (editView != null && editView is EditText) {
// Remove default input filter
(editView as EditText).filters = arrayOfNulls(0)
}
}
I am building a firmware for visually disabled people. I have to disable touch screen as and when the external key board is connected. And toggle it with Alt + T . For this I have a static volatile flag in View class called misTouchScreenEnabled. Upon plug in of external keyboard I return false from dispatchTouchEvent(). In View Class :
/*
* A register to hold the status of touch screen
*
*{#hide}
*/
public static volatile boolean misTouchScreenEnabled = false;
public boolean dispatchTouchEvent(MotionEvent event) {
if(event == null)
{
return false;
}
Log.d ("CnxsTA","View :: Into dispatchTouchEvent");
// Added by Harsh Vardhan 05102013
Configuration config = getResources().getConfiguration();
if (config.keyboard != Configuration.KEYBOARD_NOKEYS)
{
// And if the device has a hard keyboard, even if it is
// currently hidden, don't pass the touch events to the view
if(!mIsTouchScreenEnabled)
{
Log.d ("CnxsTA","View :: Into dispatchTouchEvent :: Returning False");
return false;
}
}
// Added By Harsh Vardhan
// Some Other Code...
}
In Launcher I catch the ALT + T in dispatchTouchEvent() and toggle the flag using reflection:
case KeyEvent.KEYCODE_T:
{
//Added By Harsh Vardhan 31052013
Log.d ("CnxsTA","Launcher :: onKeyDown :: T pressed");
if ((event.getMetaState() & KeyEvent.META_ALT_ON) == KeyEvent.META_ALT_ON)
{
Object s;
try
{
Log.d ("CnxsTA","Launcher :: In onKeyDown :: In isAltPressed");
s = ToogleTouchScreen(Class.forName("android.view.View"), "mIsTouchScreenEnabled");
if (s instanceof Boolean)
{
boolean v = ((Boolean) s).booleanValue();
//do something
Log.d ("CnxsTA","Launcher :: onKeyDown :: In isAltPressed :: Toggling touchscreen enable flag :: " + v);
if (accessibilityEnabled)
{
if (mTts != null)
{
if (v)
mTts.speak("Touch Screen enabled", TextToSpeech.QUEUE_FLUSH, null);
else
mTts.speak("Touch Screen disabled", TextToSpeech.QUEUE_FLUSH, null);
}
}
}
else if (s == null)
{
//do something
Log.d ("CnxsTA","Launcher :: onKeyDown :: S is Null");
}
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
//return super.dispatchKeyEvent(event);
return true;
//break;
//Added By Harsh Vardhan 31052013
}
This works well for the Launcher and its workspace, ie. the touch gets enabled and disabled when ALT + T is pressed. But the Panel in the bottom containing the back, home, recent apps and notifications remains unresponsive on touch and so is the UI in the application; though in Exploration Mode the Icons names are spoken on touch but the touch is not implemented. And it all retains to normal sate once the external keyboard is removed.
I think the reason behind this could be that the StatusBar Class and Other Classes extending the View which are taking the flag to be false only has cached the whole class or the variables and methods and is not referring to main memory where the flags have been toggled. I am aware that I have made the flag volatile.
I know that my flag gets toggle as I could confirm this from logcat. Please suggest me the direction I could solve this. Thanking in advance.