I have been following this post in order to try and understand how Android can have similar functionality to iOS Jump List
Xamarin: Vertical alphabet Indexing (Jump List) for grouped list in Xamarin forms for Android and windows UWP
I have had no problem implementing an alphabetical index using layouts, and furthermore implemented scrollto so when selecting a letter with tap, the main list will act accordingly
However, a key usability difference is that iOS, with the letters in the above example being so small, is designed to let user simply keep thumb depressed on screen and move up and down
With Android, because we're 'mimicking' this using ListView, you only have Tapped and Selected
Neither of these gives the same fluid experience as the user has to lift finger on and off screen
Is there a way to extend the list view on Android only so user can, just like iOS does out of box, slide up and down the alphabetical list and send events to the device to process?
ItemTapped="AlphaView_ItemTapped"
ItemSelected="AlphaView_ItemTapped"
You could custom ListViewRenderer and SetOnTouchListener for listview, then can detect the hold & swip method.
For example, the sample renderer code as follows:
[assembly: ExportRenderer(typeof(Xamarin.Forms.ListView), typeof(CustomListViewRenderer))]
namespace XamarinForms20.Droid
{
public class CustomListViewRenderer : ListViewRenderer, Android.Views.View.IOnTouchListener
{
Context _context;
public CustomListViewRenderer(Context context) : base(context)
{
_context = context;
}
static float mPosX = 0, mPosY = 0, mCurPosX = 0, mCurPosY = 0;
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
// TODO Auto-generated method stub
int viewheight = v.Height;
switch (e.Action) {
case MotionEventActions.Down:
mPosX = e.GetX();
mPosY = e.GetY();
Console.WriteLine("Start on " + mPosY);
break;
case MotionEventActions.Move:
mCurPosX = e.GetX();
mCurPosY = e.GetY();
Console.WriteLine("Now is moving " + mCurPosY);
break;
case MotionEventActions.Up:
if (mCurPosY - mPosY > 0
&& (Math.Abs(mCurPosY - mPosY) > 25)) {
//scroll down
Console.WriteLine("scroll down to "+ mCurPosY/viewheight);
} else if (mCurPosY - mPosY< 0
&& (Math.Abs(mCurPosY - mPosY) > 25)) {
//scroll up
Console.WriteLine("scroll up to " + mCurPosY/viewheight);
}
break;
}
return true;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
{
Control.SetOnTouchListener(this);
}
}
}
}
Related
How to show a response while gesture input is in progress?
I'm using swipe down gesture to reload my WebView.
Using this for gesture detection:How to detect swipe direction between left/right and up/down
Problem
SimpleGestureListener captures the result of user input only. It cannot be used to show a response, e.g. animation, while the user is performing a gesture.
Imperfect inelegant solution:
flaw: Shows animation independent of SimpleOnGestureListener; a response to user gesture input is displayed and gesture input may still fail or vice versa.
private volatile float userTouchY = -1;
// translating WebView every onTouch event call is inefficient.
// only translate when translateYBy value is greater than a threshold.
private float translateYBy = 0;
/**
* Shift webView without animation.
* #param num
*/
private void __shiftWebViewDownBy(int num){
float current = webView.getY();
if(current >= webviewTranslationLimit) return;
current += num;
if(current> webviewTranslationLimit) current = webviewTranslationLimit;
webView.setY(current);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
//Skip everything unless at the content top.
if(webView.getScrollY() > 0) return false;
int action = event.getAction();
if(action == MotionEvent.ACTION_MOVE){
float y = event.getY();
float dif = y - userTouchY;
if(dif < webviewTranslationLimit/10){
//dif less than screenHeight*1/40, ignore input
}else if(dif < -(webviewTranslationLimit/5)){
//swipe up
userTouchY = -1;//not swipe down cancelling
__shiftWebViewTopTo(0);
}else if(userTouchY < y) {
//swipe down
translateYBy += dif;
if(userTouchY == -1){
//userTouchY at the initial value, ignore for this once.
userTouchY = y;
}else{
userTouchY = y;
if(translateYBy > 5 ){
__shiftWebViewDownBy((int)translateYBy);
translateYBy = 0;
}
}
}
}else{
Log.d(TAG,"action not move:" +MotionEvent.actionToString(action));
// Webview shift down should only occur while moving
// Once finger is off,cancel
__shiftWebViewTopTo(0);
userTouchY = -1;
}
boolean result = gestureDetector.onTouchEvent(event);
return result;
}
Writing a GestureListener of a sort from scratch obviously solves the problem and I can simply use Toast to message the user for a failed gesture input, but there ought to be an easier, more readable solution.
I have a drag and drop list implementation in Xamarin. The problem is that I want to be able to drag and drop element only when the drag and drop button is touched not the whole element.
I was trying to detect for the ImageView being pressed but I am unable to get the correct hit ImageView.
public bool OnDown (MotionEvent e) {
int x = (int) e.GetX();
int y = (int) e.GetY();
int itemnum = PointToPosition(x, y);
if (itemnum == AdapterView.InvalidPosition) {
return false;
}
if (m_dragging != null) {
m_dragging.Stop();
m_dragging = null;
}
View item = GetChildAt(itemnum - FirstVisiblePosition);
item.Pressed = false;
var dragImage = item.FindViewById<ImageView> (Resource.Id.drag_image);
Rect bounds = new Rect();
dragImage.GetDrawingRect (bounds);
if (!bounds.Contains (x, y)) {
return false;
}
The following code works only for the first element in the list and does not apply to any other. I am suspecting that the detection of hit is incorrect.
Solved followingly. I realized that I actually do not need to care about the Y axis and it's enough to take just X axis in account.
// Detect if the user is actually pressing the drag and drop image or not.
var dragImage = item.FindViewById<ImageView> (Resource.Id.drag_image);
if (dragImage != null) {
var dragImageHitRect = new Rect ();
dragImage.GetDrawingRect (dragImageHitRect);
// In this case we do not care about the Y axis. Just compare the x axis.
if (!dragImageHitRect.Contains (x, (int)dragImage.GetY ())) {
return false;
}
}
In Android, View.onLongClickListener() takes about 1 sec to treat it as a long click. How can I configure the response time for a long click?
The default time out is defined by ViewConfiguration.getLongPressTimeout().
You can implement your own long press:
boolean mHasPerformedLongPress;
Runnable mPendingCheckForLongPress;
#Override
public boolean onTouch(final View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
if (mPendingCheckForLongPress != null) {
v.removeCallbacks(mPendingCheckForLongPress);
}
// v.performClick();
}
break;
case MotionEvent.ACTION_DOWN:
if( mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new Runnable() {
public void run() {
//do your job
}
};
}
mHasPerformedLongPress = false;
v.postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
if ((x < 0 - slop) || (x >= v.getWidth() + slop) ||
(y < 0 - slop) || (y >= v.getHeight() + slop)) {
if (mPendingCheckForLongPress != null) {
v. removeCallbacks(mPendingCheckForLongPress);
}
}
break;
default:
return false;
}
return false;
}
This may be late to answer but in case of someone needed.
You can change the time out if your phone is rooted.
simply set the value in database to desired time out value. This will effect system wide long press event.
directory: /data/data/com.android.providers.settings/databases
file : settings.db
table : secure
name : long_press_timeout
By coding ? you may need system app privilege. not so sure actually.
but if you back track the ViewConfiguration.getLongPressTimeout() method, you will see that the constant value is coming from Settings.Secure.LONG_PRESS_TIMEOUT which is a mapping for settings.db
time out set operation is written in View class so it effects every view.
I've been exploring into Nim Game on Android. The players are going to take objects from heaps. I use openGLES to draw the objects and heaps. Where I'm stuck is how to "take".
As the samples shown on official dev guide
, I can override onTouchEvent method in the class that extends GLSurfaceView for the interaction. However, how I could tell where the objects have been drawn? Or are there any objects at the coordinates where I touch?
Any ideas?
Thx in advance!
If I'm understanding your question correctly, it sounds like you want to do some simple collision detection to see if your touch point is inside one of the objects on the heap. You can do this with some basic math between the coordinates of the touch point and the center coordinates which you used to draw the object.
For instance, assuming your objects are rectangles, this would be the general idea:
boolean detectCollision(Object object, TouchPoint touch) {
return object.x - object.width/2 <= touch.x &&
object.x + object.width/2 >= touch.x &&
object.y - object.height/2 <= touch.y &&
object.y + object.height/2 >= touch y;
}
You could then iterate through all of the objects in your heaps and if this returns true for any of them, then you know your touchpoint is inside of that object and can proceed to call whatever you need to call on it.
Keep in mind that the touch coordinates the system gives you will be screen coordinates, so you have to take into account any discrepancies between the screen coordinate system and the coordinate system you defined with your view frustum.
public class Main extends Activity implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
synchronized (this) {
if (!_isPaused) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
_touchedX = event.getX();
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
float touchedX = event.getX();
float dx = Math.abs(_touchedX - touchedX);
_dxLowPassed = lowPass(dx, _dxLowPassed);
switch (_screenUsage) {
case HALF_SCREEN:
if (touchedX < _width / 2) {
if(touchedX < _touchedX) {
_zAngle = (2 * _dxLowPassed / _width) * TOUCH_SENSITIVITY * ANGLE_SPAN;
_zAngleLowPassed = lowPass(_zAngle, _zAngleLowPassed);
GLES20Renderer._zAngle = GLES20Renderer._zAngle + _zAngleLowPassed;
}
} else {
if( touchedX > _touchedX ) {
_zAngle = (2 * _dxLowPassed / _width) * TOUCH_SENSITIVITY * ANGLE_SPAN;
_zAngleLowPassed = lowPass(_zAngle, _zAngleLowPassed);
GLES20Renderer._zAngle = GLES20Renderer._zAngle - _zAngleLowPassed;
}
}
Log.d("TOUCH", new Float(_zAngleLowPassed).toString());
break;
Can anyone help please.
Im writing a small Android game where the player is able to select a "barrier" and drag it accross the screen with their finger. I have the barriers drawn on the screen and I am able to drag it accross the screen.
My problem however is when I add more than 1 barrier, eg 3 barriers, and drag a barrier accross the screen, they all drag and they all drag to the same position. That is to say they all lie on top of each other, making it look as though there is only 1 barrier.
Here is my code, can anyone please tell me where I am going wrong/explain where I am going wrong.
public class MainGamePanel extends SurfaceView implements SurfaceHolder.Callback, SensorEventListener {
// Initialising the Barrier
private Barrier barrier[] = new Barrier[3];
// The Main Game Panel
public MainGamePanel(Context context) {
super(context);
// Adding a call-back (this) to the surfaceHolder to intercept events
getHolder().addCallback(this);
// Creating the Game items
// The starting coordinates of the Barrier
int x = 30;
int y = 270;
barrier[0] = new Barrier(BitmapFactory.decodeResource(getResources(), R.drawable.blue_barrier), x, y);
barrier[1] = new Barrier(BitmapFactory.decodeResource(getResources(), R.drawable.green_barrier), x + 15, y);
barrier[2] = new Barrier(BitmapFactory.decodeResource(getResources(), R.drawable.pink_barrier), x + 30, y);
// Create the Game Loop Thread
thread = new MainThread(getHolder(), this);
// Make the GamePanel focusable so it can handle events
setFocusable(true);
}
// Handles the touch events
public boolean onTouchEvent(MotionEvent event)
{
int eventAction = event.getAction();
int x = (int)event.getX();
int y = (int)event.getY();
switch (eventAction)
{
// Touch down so check if finger is on Barrier
case MotionEvent.ACTION_DOWN:
if (x > barrier[0].getX() && x < barrier[0].getX() + 8
&& y > barrier[0].getX() && y < barrier[0].getY() + 8)
{
barrier[0].isTouched();
}
else if (x > barrier[1].getX() && x < barrier[1].getX() + 8
&& y > barrier[1].getX() && y < barrier[1].getY() + 8)
{
barrier[1].isTouched();
}
else if (x > barrier[2].getX() && x < barrier[2].getX() + 8
&& y > barrier[2].getX() && y < barrier[2].getY() + 8)
{
barrier[2].isTouched();
}
break;
// Touch-drag with the Barrier
case MotionEvent.ACTION_MOVE:
// Move the Barrier the same as the finger
for (int i = 0; i < barrier.length; i++)
{
if (barrier[i] == barrier[0])
{
barrier[0].setX(x);
barrier[0].setY(y);
} // end if
else if (barrier[i] == barrier[1])
{
barrier[1].setX(x);
barrier[1].setY(y);
}
else if (barrier[i] == barrier[2])
{
barrier[2].setX(x);
barrier[2].setY(y);
} // end else if
} // end for
break;
case MotionEvent.ACTION_UP:
// Finger no longer on Barrier - Do Nothing
break;
}
return true;
}
// Render - Draws the Game Item Bitmaps to the screen
public void render(Canvas canvas)
{
// Set the background to white
canvas.drawColor(Color.WHITE);
barrier[0].draw(canvas);
barrier[1].draw(canvas);
barrier[2].draw(canvas);
}
// Update
// This is the Game's update method
// It iterates through all the Objects and calls their update() methods (if they have one)
public void update()
{
} // end update
In your barrier move code, you aren't checking a particular barrier is touched, so you're moving all of them to the same coordinates. The following loop does exactly the same thing as your current code:
// Move the Barrier the same as the finger
for (int i = 0; i < barrier.length; i++)
{
barrier[i].setX(x);
barrier[i].setY(y);
} //end for
To fix this, you need to check if the current barrier in the loop is the one that was touched, and can change that whole loop to something like:
// Move the Barrier the same as the finger
for (int i = 0; i < barrier.length; i++)
{
if (barrier[i].isTouched())
{
barrier[i].setX(x);
barrier[i].setY(y);
} // end if
} // end for
You would then need to make sure that you un-set the touched property in the ACTION_UP section. If you post your Barrier class definition I can probably help more.
I'm pretty sure your problem could be solved by making the barriers three separate objects instead of in an array.
As already pointed out, your code in handling the move is wrong because it moves all the Barriers to the same X,Y when it should only move the one that is touched.
Also, you are never resetting the isTouched on the objects in the Action up. When the user lifts their finger you should set them all to isTouched == false. If you don't do that then once you touch one it will always move to the X,Y.