Programmatically animate Android BrokenView - android

I was going through some of the awesome Android libraries and I found the Android BrokenView. It's quite simple to use and uses the touch events to show the broken animation. Here's the code that we're required to add in order to enable a View to behave as a BrokenView:
brokenView = BrokenView.add2Window(context);
listener = new BrokenTouchListener.Builder(brokenView).build();
view.setOnTouchListener(listener);
The problem that I've is that I want to perform the broken animation programmatically i.e, without any actual touch events. I tried looking at the source code but couldn't figure out how to achieve that as most of the methods are protected.
Any ideas on how can I do that?

I've achieved this using a motion event. It works, but I'd prefer to call createAnimator() directly, as I don't want the user to be able to trigger the smash via a touch event.
long downTime = SystemClock.uptimeMillis();
long eventTime = SystemClock.uptimeMillis()+1000;
int[] coords = new int[2];
targetView.getLocationOnScreen(coords);
float x = coords[0] + targetView.getWidth()/2;
float y = coords[1] + targetView.getHeight()/2;
MotionEvent motionEvent = MotionEvent.obtain(
downTime,
eventTime,
MotionEvent.ACTION_DOWN,
x,
y,
0
);
mBrokenTouchListener = new BrokenTouchListener.Builder(mBrokenView)
.setEnableArea(mRootView)
.build();
mBrokenTouchListener.onTouch(targetView, motionEvent);
What I'd like to do is this:
Point point = new Point((int)x, (int)y);
BrokenConfig config = new BrokenConfig();
brokenView.createAnimator(mFLTargetView, point, config);
brokenView.setEnable(true);
// or
brokenView.start();
but BrokenConfig is package private, and there's no start() method.
HTH

Related

performClick not working when using spinner library

I have implemented this spinner in my app. I'm displaying a dialog and I have my spinner in that dialog. I want my spinner to open as the dialog comes up. I have tried performClick() event right after setAdapter() method but it doesn't seem to work.
My code looks like this:
final MaterialBetterSpinner selectBrandForSODRating = dialog.findViewById(R.id.selectBrandForSODspinner);
final ArrayList<Products> brandList = new ArrayList<Products>();
Cursor crsCheckSODData = database.rawQuery(myQuery, null);
if(crsCheckSODData.getCount() > 0){
while (crsCheckSODData.moveToNext()) {
//data...
brandList.add(data);
}
}
crsCheckSODData.close();
final ArrayAdapter<Products> SODBrandAdapter = new ArrayAdapter<Products>(myView.this, android.R.layout.simple_dropdown_item_1line, brandList);
selectBrandForSODRating.setAdapter(SODBrandAdapter);
selectBrandForSODRating.performClick(); //this right here isnt working...
If you are using https://github.com/Lesilva/BetterSpinner, the code is overriding onTouchEvent here, but not overriding onClick. That's why performClick will not work.
You could simulate touch event instead to the view. Here's how:
int[] coords = new int[2];
selectBrandForSODRating.getLocationOnScreen(coords);
float x = (float) coords[0];
float y = (float) coords[1];
// List of meta states found here: developer.android.com/reference/android/view/KeyEvent.html#getMetaState()
int metaState = 0;
// Obtain MotionEvent object
long downTime1 = SystemClock.uptimeMillis();
long eventTime1 = SystemClock.uptimeMillis() + 100;
MotionEvent motionEventDown = MotionEvent.obtain(
downTime1,
eventTime1,
MotionEvent.ACTION_DOWN,
x,
y,
metaState
);
// Dispatch touch event to view
selectBrandForSODRating.dispatchTouchEvent(motionEventDown);
long downTime2 = SystemClock.uptimeMillis();
long eventTime2 = SystemClock.uptimeMillis() + 100;
MotionEvent motionEventUp = MotionEvent.obtain(
downTime2,
eventTime2,
MotionEvent.ACTION_UP,
x,
y,
metaState
);
// Dispatch touch event to view
selectBrandForSODRating.dispatchTouchEvent(motionEventUp);
You could also directly call the methods from here
selectBrandForSODRating.requestFocus();
selectBrandForSODRating.showDropDown();
But I don't recommend it, because there is another boolean state, isPopUp, that has private access and you can't access.

How to send out pointer event in Android

I'm trying to detect the virtual keyboard height in Android.
I found a similar topic: Get the height of virtual keyboard in Android
It seems the author found a way to detect the height:
I found a way to get it. After I request to open virtual keyboard, I
send pointer event that I generate. their y coordinate starts from
height of device and decreases.
I don't understand how to do that.
I'll be using the code provided at the link that you posted:
// Declare Variables
int softkeyboard_height = 0;
boolean calculated_keyboard_height;
Instrumentation instrumentation;
// Initialize instrumentation sometime before starting the thread
instrumentation = new Instrumentation();
mainScreenView is your base view, your activity's view. m(ACTION_DOWN) and m1(ACTION_UP) are touch events that are dispatched using Instrumentation#sendPointerSync(MotionEvent). The logic is that a MotionEvent dispatched to where the keyboard is being displayed will cause the following SecurityException:
java.lang.SecurityException: Injecting to another application requires
INJECT_EVENTS permission
So, we start at the bottom of the screen and make our way up (by decrementing y) on every iteration of the loop. For certain number of iterations, we will get a SecurityException (which we'll catch): this would imply that the MotionEvent is happening over the keyboard. The moment y gets small enough (when its just above the keyboard), we'll break out of the loop and calculate the keyboard's height using:
softkeyboard_height = mainScreenView.getHeight() - y;
Code:
Thread t = new Thread(){
public void run() {
int y = mainScreenView.getHeight()-2;
int x = 10;
int counter = 0;
int height = y;
while (true){
final MotionEvent m = MotionEvent.obtain(
SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
MotionEvent.ACTION_DOWN,
x,
y,
1);
final MotionEvent m1 = MotionEvent.obtain(
SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP,
x,
y,
1);
boolean pointer_on_softkeyboard = false;
try {
instrumentation.sendPointerSync(m);
instrumentation.sendPointerSync(m1);
} catch (SecurityException e) {
pointer_on_softkeyboard = true;
}
if (!pointer_on_softkeyboard){
if (y == height){
if (counter++ < 100){
Thread.yield();
continue;
}
} else if (y > 0){
softkeyboard_height = mainScreenView.getHeight() - y;
Log.i("", "Soft Keyboard's height is: " + softkeyboard_height);
}
break;
}
y--;
}
if (softkeyboard_height > 0 ){
// it is calculated and saved in softkeyboard_height
} else {
calculated_keyboard_height = false;
}
}
};
t.start();
Instrumentation#sendPointerSync(MotionEvent):
Dispatch a pointer event. Finished at some point after the recipient
has returned from its event processing, though it may not have
completely finished reacting from the event -- for example, if it
needs to update its display as a result, it may still be in the
process of doing that.
Use OnGlobalLayoutListener, it works perfectly for me.

Android touch events are always InputDevice.SOURCE_TOUCHSCREEN?

I have made an event injector for Android based on code from the AndroidScreencast project. I'm trying to simulate the Xperia Play touchpad on other devices. My code works great when I'm simulating normal touchscreen events, but does something unexpected when I try to actually simulate the touchpad. Here's my code:
MotionEvent.PointerCoords[] coords = { new MotionEvent.PointerCoords() };
coords[0].x = 200;
coords[0].y = 200;
int[] ptrs = { 0 };
MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), action, 1, ptrs, coords, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHPAD, 0);
windowManager.injectPointerEvent(event, false);
Here's the strange part: the event is sent, but in my little testing app, it shows up as SOURCE_TOUCHSCREEN!
Am I doing something wrong, or is this some Android quirk where it converts non-supported sources?

Get MotionEvent.getRawX/getRawY of other pointers

Can I get the value of MotionEvent.getRawX()/getRawY() of other pointers ?
MotionEvent.getRawX() api reference
The api says that uses getRawX/getRawY to get original raw X/Y coordinate, but it only for 1 pointer(the last touched pointer), is it possible to get other pointer's raw X/Y coordinate ?
Indeed, the API doesn't allow to do this, but you can compute it. Try that :
public boolean onTouch(final View v, final MotionEvent event) {
int rawX, rawY;
final int actionIndex = event.getAction() >> MotionEvent.ACTION_POINTER_ID_SHIFT;
final int location[] = { 0, 0 };
v.getLocationOnScreen(location);
rawX = (int) event.getX(actionIndex) + location[0];
rawY = (int) event.getY(actionIndex) + location[1];
}
A solution worth trying for most use cases is to add this to the first line of the onTouchEvent it simply finds the difference between the raw and processed, and shifts the location of the MotionEvent event, by that amount. So that all the getX(int) values are now the raw values. Then you can actually use the getX() getY() stuff as the Raw values.
event.offsetLocation(event.getRawX()-event.getX(),event.getRawY()-event.getY());
While Ivan's point is valid, it's simply the case that applying a matrix directly to the view itself sucks so bad you likely shouldn't do it. It's weird and inconsistent between devices, cause the touch events to fall out of view and get declined, etc. If you are moving a view around like that you are better off simply overloading the onDraw() and applying that matrix to the canvas, then applying the inverse matrix to the MotionEvent so everything meshes up right. Then you can properly react to the events with proper and fine grain control. And, if you do that, my solution here wouldn't be subject to Ivan's objection.
It might be not enough to just shift local coordinates by a view's location if the view is rotated. In this case you need something like this:
void getRowPoint(MotionEvent ev, int index, PointF point){
final int location[] = { 0, 0 };
getLocationOnScreen(location);
float x=ev.getX(index);
float y=ev.getY(index);
double angle=Math.toDegrees(Math.atan2(y, x));
angle+=getRotation();
final float length=PointF.length(x,y);
x=(float)(length*Math.cos(Math.toRadians(angle)))+location[0];
y=(float)(length*Math.sin(Math.toRadians(angle)))+location[1];
point.set(x,y);
}
The getLocationOnScreen answer works most of the time, but I was seeing it return incorrect values sometimes (when I was repositioning and re-parenting the view while the touch event was taking place), so I found an alternate approach that works more reliably.
If you look at the implementation of getRawX, it calls a private native function that accepts a pointerIndex, but the MotionEvent class only ever calls it with index 0:
public final float getRawX() {
return nativeGetRawAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT);
}
Unfortunately, nativeGetRawAxisValue is private, but you can hack around that by using reflection to give yourself access to everything you need. Here's what the code looks like:
private Point getRawCoords(MotionEvent event, int pointerIndex) {
try {
Method getRawAxisValueMethod = MotionEvent.class.getDeclaredMethod(
"nativeGetRawAxisValue", long.class, int.class, int.class, int.class);
Field nativePtrField = MotionEvent.class.getDeclaredField("mNativePtr");
Field historyCurrentField = MotionEvent.class.getDeclaredField("HISTORY_CURRENT");
getRawAxisValueMethod.setAccessible(true);
nativePtrField.setAccessible(true);
historyCurrentField.setAccessible(true);
float x = (float) getRawAxisValueMethod.invoke(null, nativePtrField.get(event),
MotionEvent.AXIS_X, pointerIndex, historyCurrentField.get(null));
float y = (float) getRawAxisValueMethod.invoke(null, nativePtrField.get(event),
MotionEvent.AXIS_Y, pointerIndex, historyCurrentField.get(null));
return new Point((int)x, (int)y);
} catch (NoSuchMethodException|IllegalAccessException|InvocationTargetException|
NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
Of course, the MotionEvent internals aren't documented, so this approach might crash on past or future versions of the SDK, but it seems to be working for me.
Edit: It looks like the type of mNativePtr and the nativePtr param changed from int to long in API level 20, so if you're targeting API level 19 or earlier, the above code will crash because getDeclaredMethod won't find anything. To fix this in my code, I just fetched the method by name instead of full type signature, which happens to work in this case. There isn't a way to directly look up methods with a given name, so I looped through the declared methods at static init time and saved the matching one to a static field. Here's the code:
private static final Method NATIVE_GET_RAW_AXIS_VALUE = getNativeGetRawAxisValue();
private static Method getNativeGetRawAxisValue() {
for (Method method : MotionEvent.class.getDeclaredMethods()) {
if (method.getName().equals("nativeGetRawAxisValue")) {
method.setAccessible(true);
return method;
}
}
throw new RuntimeException("nativeGetRawAxisValue method not found.");
}
Then I used NATIVE_GET_RAW_AXIS_VALUE in place of the getRawAxisValueMethod in the above code.
There is no API to get pointer's specific RawX and RawY.
But you can calculate similar values with regards to View's position to the parent and its rotation.
In case if parent view occupies the entire touch area you are trying to handle, using Matrix will help you to solve your problem:
private float[] calcRawCoords(MotionEvent event, int pointerIndex) {
Matrix screenMatrix = new Matrix();
screenMatrix.postRotate(getRotation(), mPivotX, mPivotY);
screenMatrix.postTranslate(getLeft(), getTop());
float viewToScreenCoords[] = {event.getX(pointerIndex), event.getY(pointerIndex)};
screenMatrix.mapPoints(viewToScreenCoords);
return viewToScreenCoords;
}

How to have scrolling animation programmatically

I'm trying to implement scroll animation for gallery programmatically.
Tried with setSelection(int pos, Boolean animate) and it's not working.
Is there anyway to override setSelection() method.
Just now I have got this problem. I was need to move just one element of the gallery, so the best solution to me was to emulate key down event
myGallery.onKeyDown(KeyEvent.KEYCODE_DPAD_RIGHT, null);
or
myGallery.onKeyDown(KeyEvent.KEYCODE_DPAD_LEFT, null);
Gallery.setSelection(int position, boolean animate);
Refer below URL:
http://groups.google.com/group/android-developers/browse_thread/thread/9140fd6af3061cdf/7f89e53ae53e455b?lnk=gst&q=setselection#7f89e53ae53e455b
Soln:
If you're still looking, I have two possible solutions for you, both
slightly unpleasant:
(1) You can make the gallery do a fling with a chosen velocity, thus:
myGallery.onFling(null, null, velocity, 0);
By tweaking the velocity, you can set up values to move the selection
by one or two in either direction. As the Gallery self-centers, you do
not need to get the destination exactly right.
(2) As the Gallery source is available, you can modify it in order to
implement your own Gallery. It doesn't look as though you need to add
much code to be able to control a fling so as to end at your chosen
selection.
I thought I was going to have to do (2), but found I could get away
with (1) for my problem.
Based on Kurru's excellent thinking of simulating clicking next or previous view.
//scroll forward or backward
private void scroll(int type){
View selectedV = mG.getSelectedView();
int idx = mG.indexOfChild(selectedV);
switch(type){
case FORWARD:
default:
if(idx<mG.getChildCount()-1)
idx++;
break;
case BACKWARD:
if(idx>0)
idx--;
break;
}
//now scrolled view's child idx in gallery is gotten
View nextView = mG.getChildAt(idx);
//(x,y) in scrolled view is gotten
int x = nextView.getLeft()+nextView.getWidth()/2;
int y = nextView.getTop()+nextView.getHeight()/2;
String out = String.format("x=%d, y=%d", x, y);
Log.i(TAG+".scroll", out);
//Kurru's simulating clicking view
MotionEvent event = MotionEvent.obtain(100, 100, MotionEvent.ACTION_DOWN, x, y, 0);
mG.onDown(event);
boolean res = mG.onSingleTapUp(null);
Log.i(TAG+".scroll", "onSingleTapUp return =" + res);
}
I was looking through the Gallery source to see if I could get this feature. It looks like something is possible with this code. However I gave up before I could get it working. It seems like I wasn't passing in the correct coordinates so res always returned false. Would return true if it worked.
Just posting this here in-case someone else wants to give a go at fixing it! (Please post your solution if you manage it!)
Rect rect = new Rect();
gallery.getHitRect(rect);
int x = rect.centerX()+getWindowManager().getDefaultDisplay().getWidth();
int y = rect.centerY();
MotionEvent event = MotionEvent.obtain(100, 100, MotionEvent.ACTION_DOWN, x, y, 0);
timesGallery.onDown(event);
boolean res = timesGallery.onSingleTapUp(null);
I made little change in code given by "Kurru". this is working now
Rect rect = new Rect();
gallery.getHitRect(rect);
int width = Math.abs(rect.width());
if(!isForwardScroll){
width = width * -1;
}
int x = rect.centerX()+width/2;
int y = rect.centerY();
MotionEvent event = MotionEvent.obtain(100, 100, MotionEvent.ACTION_DOWN, x, y, 0);
gallery.onDown(event);
boolean res = gallery.onSingleTapUp(null);

Categories

Resources