I have an Adapter that extends RecyclerView.Adapter. Each line of adapter is a custom FrameLayout with GestureDetector implemented. The FrameLayout xml file has 2 RelativeLayout.
In this FrameLayout I redirect X and Y positions to the RelativeLayout that is in the foreground.
In each RelativeLayout I have a setOnClickListener. This is because if user clicks the RelativeLayout that is on the foreground, an activity is started. If the user scrolls the former, the latter is available to click.
What I'm facing is:
Implementing setOnClickListener on these layouts I lost view scroll feature of my custom FrameLayout. And I need the scroll feature and clickListener for each view.
This is my xml
file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:id="#+id/background_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/work_order_deleted_item_background_color">
...
</RelativeLayout>
<RelativeLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white"
android:visibility="invisible"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin">
...
</RelativeLayout>
</FrameLayout>
My custom FrameLayout:
public class SwipeView extends FrameLayout implements GestureDetector.OnGestureListener{
private RelativeLayout container;
private RelativeLayout backgroundContainer;
private GestureDetector detector;
private float posX;
private float posY;
private float lastTouchX;
private float lastTouchY;
private float xLimit;
private int mActivePointerId;
public SwipeView(#NonNull Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.adapter_work_order, this);
initViews(context, null);
xLimit = 200.0f;
mActivePointerId = INVALID_POINTER_ID;
detector = new GestureDetector(this);
}
public SwipeView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
initViews(context, attrs);
xLimit = 200.0f;
mActivePointerId = INVALID_POINTER_ID;
detector = new GestureDetector(this);
}
public SwipeView(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViews(context, attrs);
xLimit = 200.0f;
mActivePointerId = INVALID_POINTER_ID;
detector = new GestureDetector(this);
}
private void initViews(Context context, AttributeSet attrs){
this.container = this.findViewById(R.id.container);
this.backgroundContainer = this.findViewById(R.id.background_container);
this.checkboxDownload = this.findViewById(R.id.checkbox_download);
this.textviewWo = this.findViewById(R.id.textview_wo);
this.downloadedWo = this.findViewById(R.id.textview_downloaded_wo);
this.textviewTime = this.findViewById(R.id.textview_time);
this.textviewField1 = this.findViewById(R.id.textview_field_1);
this.textviewField2 = this.findViewById(R.id.textview_field_2);
this.textviewField3 = this.findViewById(R.id.textview_field_3);
this.deleteLabel = this.backgroundContainer.findViewById(R.id.delete_wo);
}
#Override
public boolean onTouchEvent(MotionEvent event){
final int action = MotionEventCompat.getActionMasked(event);
switch (action) {
case MotionEvent.ACTION_DOWN: {
final int pointerIndex = MotionEventCompat.getActionIndex(event);
final float x = MotionEventCompat.getX(event, pointerIndex);
final float y = MotionEventCompat.getY(event, pointerIndex);
// Remember where we started (for dragging)
lastTouchX = x;
lastTouchY = y;
// Save the ID of this pointer (for dragging)
mActivePointerId = MotionEventCompat.getPointerId(event, 0);
if(x > container.getRight() + container.getX()){
Log.e("ERROR", "TOUCHED OUTSIDE");
}
else {
Log.e("ERROR", "TOUCHED INSIDE: ");
}
break;
}
case MotionEvent.ACTION_MOVE: {
// Find the index of the active pointer and fetch its position
final int pointerIndex = MotionEventCompat.findPointerIndex(event, mActivePointerId);
final float x = MotionEventCompat.getX(event, pointerIndex);
final float y = MotionEventCompat.getY(event, pointerIndex);
// Calculate the distance moved
final float dx = x - lastTouchX;
final float dy = y - lastTouchY;
posX += dx;
posY += dy;
if(posX <= -xLimit){
posX -= dx;
}
if(posX > 0.0f){
posX = 0.0f;
}
invalidate();
// Remember this touch position for the next move event
lastTouchX = x;
lastTouchY = y;
container.setX(posX);
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
}
return true;
}
#Override
public boolean onDown(MotionEvent motionEvent) {
return true;
}
#Override
public void onShowPress(MotionEvent motionEvent) {
}
#Override
public boolean onSingleTapUp(MotionEvent motionEvent) {
return true;
}
#Override
public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
return true;
}
#Override
public void onLongPress(MotionEvent motionEvent) {
}
#Override
public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float velocityX, float velocityY) {
return true;
}
}
And this code is inside my adapter:
holder.container.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//onItemClickListener.onItemClick(workOrder);
Log.e("ERROR", "FOREGROUND CLICKED");
}
});
holder.backgroundContainer.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view){
Log.e("ERROR", "BACKGROUND CLICKED");
}
});
Finally this is the code that is inside my Activity:
((MyAdapter) recyclerview.getAdapter())
.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
#Override
public void onItemClick(Item item) {
if(!presenter.getDownloadAvailability()) {
...
}
}
});
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp"
android:fillViewport="false">
<RelativeLayout
android:id="#+id/background_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/work_order_deleted_item_background_color">
...
</RelativeLayout>
</ScrollView>
Related
I am making a project. It draws concentric circles in android canvas. When the user drags the screen, all the circles move accordingly. Here is my code so far.
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:background="#android:color/white">
<RelativeLayout
android:id="#+id/scrollableSpace"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true">
<project.myProject.DrawOrbit
android:id="#+id/orbitsRegion"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true" />
</RelativeLayout>
</RelativeLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnTouchListener
{
PointF center;
center.x=500;center.y=500;
float radius[]={100,200,300,400,500};
DrawOrbit orbit;
int startX=0,startY=0,currentX=0,currentY=0;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RelativeLayout layout=(RelativeLayout)findViewById(R.id.scrollableSpace);
orbit.draw(center,radius);
layout.setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent motionEvent)
{
final int action= motionEvent.getAction();
switch(action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
{
startX=(int)motionEvent.getRawX();
startY=(int)motionEvent.getRawY();
break;
}
case MotionEvent.ACTION_MOVE:
{
currentX=(int)motionEvent.getRawX();
currentY=(int)motionEvent.getRawY();
float diffX=currentX-startX;
float diffY=currentY-startY;
startX=currentX;
startY=currentY;
center.x+=diffX;
center.y+=diffY;
orbit.draw(center,radius);
break;
}
}
return true;
}
}
DrawOrbit.java
public class DrawOrbit extends View
{
PointF center;
float radius[];
public DrawOrbit(Context context) {
super(context);
}
public DrawOrbit(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public DrawOrbit(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
int len=radius.length;
for(int a=0;a<len;a++)
{
canvas.drawCircle(center.x,center.y,radius[a],paint);
}
}
public void draw(PointF center, float radius[])
{
this.center=center;
this.radius=radius;
invalidate();
requestLayout();
}
}
What I want to do is that the circles should appear one by one. First the inner most circle then the next one after some delay then the next and so on. The same effect should be seen when the screen is dragged. How can I achieve this? Any help will be appreciated.
What you're looking for is a timeout. I'd suggest creating a new thread to draw everything, and start by drawing the first circle, have the method look like this:
public void drawCircle() {
//Draw logic
TimeUnit.SECONDS.sleep(3);
drawNextCircle();
}
//Assuming on java 1.8+
Thread thread = new Thread() => {
drawCircle();
}
What this will do is have the thread sleep for 3 seconds, and then continue normal operation after the time period is up. You can change it to other measurements of time such as TimeUnit.MILLISECONDS or TimeUnit.MINUTES as well.
edit: You likely won't want this in your main thread, as it will stop the ENTIRE application from working for however long you put the thread on timeout, so it's almost essential for this to work that you put it in a separate thread.
edit 2: It would make more sense to add a separate util method to timeout and then call another method via reflection than the above code, but the same code would be needed.
Figured out the answer to my own question. I had to use a Handler, multiplied the delay with the for-loop variable and made 1 then 2 then so on.. circles to get the desired effect. Here is the code.
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnTouchListener
{
PointF center;
center.x=500;center.y=500;
float radius[]={100,200,300,400,500};
DrawOrbit orbit;
int startX=0,startY=0,currentX=0,currentY=0;
Handler handler1 = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RelativeLayout layout=(RelativeLayout)findViewById(R.id.scrollableSpace);
for (int a = 0; a<radius.length ;a++)
{
final int index=a;
handler1.postDelayed(new Runnable() {
#Override
public void run() {
orbit.draw(center,radius,index);
}
}, 300 * a);
}
layout.setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent motionEvent)
{
final int action= motionEvent.getAction();
switch(action & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
{
startX=(int)motionEvent.getRawX();
startY=(int)motionEvent.getRawY();
break;
}
case MotionEvent.ACTION_MOVE:
{
currentX=(int)motionEvent.getRawX();
currentY=(int)motionEvent.getRawY();
float diffX=currentX-startX;
float diffY=currentY-startY;
startX=currentX;
startY=currentY;
center.x+=diffX;
center.y+=diffY;
break;
}
case MotionEvent.ACTION_UP:
{
for (int a = 0; a<radius.length ;a++)
{
final int index=a;
handler1.postDelayed(new Runnable() {
#Override
public void run() {
orbit.draw(center,radius,index);
}
}, 300 * a);
}
break;
}
}
return true;
}
}
DrawOrbit.java
public class DrawOrbit extends View
{
PointF center;
float radius[];
int index;
public DrawOrbit(Context context) {
super(context);
}
public DrawOrbit(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public DrawOrbit(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
int len=radius.length;
for(int a=0;a<index;a++)
{
canvas.drawCircle(center.x,center.y,radius[a],paint);
}
}
public void draw(PointF center, float radius[],int index)
{
this.center=center;
this.radius=radius;
this.index=index;
invalidate();
requestLayout();
}
}
I am drawing on android.graphics.Canvas using android.graphics.Path. I want to allow the user to draw using a range of colors and stroke widths, however, whenever the color or stroke width is changed, everything is re-drawn using the new color or stroke width. How can I fix this?
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/btnW10"
android:layout_margin="3sp"
android:layout_weight="1"
android:text="Width10"/>
<Button
android:id="#+id/btnW40"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="3sp"
android:layout_weight="1"
android:text="Width40"/>
<Button
android:id="#+id/btnW70"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="3sp"
android:layout_weight="1"
android:text="Width70"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="20dp"></LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="3sp"
android:layout_weight="1"
android:id="#+id/btnBlue"
android:text="Blue"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="3sp"
android:layout_weight="1"
android:id="#+id/btnRed"
android:text="Red"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="3sp"
android:layout_weight="1"
android:id="#+id/btnGreen"
android:text="Green"/>
</LinearLayout>
<com.vladislav.canvaswc.DrawLine
android:id="#+id/drwLine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
main.java:
public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
DrawLine dr;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnB=(Button) findViewById(R.id.btnBlue);
Button btnG=(Button) findViewById(R.id.btnGreen);
Button btnR=(Button) findViewById(R.id.btnRed);
Button btnW10=(Button) findViewById(R.id.btnW10);
Button btnW40=(Button) findViewById(R.id.btnW40);
Button btnW70=(Button) findViewById(R.id.btnW70);
dr =(DrawLine) findViewById(R.id.drwLine);
btnB.setOnClickListener(this);
btnG.setOnClickListener(this);
btnR.setOnClickListener(this);
btnW10.setOnClickListener(this);
btnW40.setOnClickListener(this);
btnW70.setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch(v.getId())
{
case R.id.btnBlue : dr.changeColor(Color.BLUE);break;
case R.id.btnGreen : dr.changeColor(Color.GREEN);break;
case R.id.btnRed : dr.changeColor(Color.RED);break;
case R.id.btnW10 : dr.changeWidth(10f);break;
case R.id.btnW40 : dr.changeWidth(40f);break;
case R.id.btnW70 : dr.changeWidth(70f);break;
}
}
}
drawline.java ChangeWidthColor its my interface with 2 methods changeColor()
and changeWidth()
public class DrawLine extends View implements ChangeWidthColor {
private Paint paint = new Paint();
private Path path = new Path();
public float x;
public float y;
public DrawLine(Context context) {
super(context);
init(context);
}
public DrawLine(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DrawLine(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public void init(Context context) {
//paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.MITER);
//paint.setStrokeWidth(5f);
}
#Override
public void changeColor(int color) {
paint.setColor(color);
}
#Override
public void changeWidth(float width) {
paint.setStrokeWidth(width);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path,paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x= event.getX();
y= event.getY();
path.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
x= event.getX();
y= event.getY();
path.lineTo(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
invalidate();
break;
}
invalidate();
return true;
}
}
This solution maintains a List of Path objects and a List of Integers for storing colors. These Lists are in parallel - 1 entry in each list for a Path and color pair. onDraw iterates these Lists, drawing each Path with the corresponding color.
Each time the user clicks to change the color, a new Path is created. ACTION_DOWN and ACTION_MOVE call moveTo(x,y) and lineTo(x,y) on the path, and ACTION_UP causes the Path and current color to be added to the lists.
Note: I have not implemented a solution for stroke width, however, you should be able to follow the example and add this yourself.
public class DrawLine extends View {
private Path path = new Path();
private Paint paint = new Paint();
private int currentColor = Color.BLACK;
private List<Path> paths = new ArrayList<Path>();
private List<Integer> colors = new ArrayList<Integer>();
private float x;
private float y;
public DrawLine(Context context) {
super(context);
init(context);
}
public DrawLine(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DrawLine(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public void init(Context context) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.MITER);
paint.setColor(currentColor);
}
public void changeColor(int color) {
currentColor = color;
path = new Path();
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int x = 0; x < paths.size(); x++) {
paint.setColor(colors.get(x));
canvas.drawPath(paths.get(x), paint);
}
}
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = event.getX();
y = event.getY();
path.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
x = event.getX();
y = event.getY();
path.lineTo(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
paths.add(path);
colors.add(currentColor);
invalidate();
break;
}
invalidate();
return true;
}
}
`
public class DrawLine extends View implements ChangeWidthColor {
private Path path = new Path();
private Paint paint = new Paint();
private int currentColor = Color.BLACK;
private float width = 1f;
public float x;
public float y;
private LinkedList<Integer> color = new LinkedList<Integer>();
private LinkedList<Float> widthSize = new LinkedList<Float>();
private LinkedList<Path> paths = new LinkedList<Path>();
public DrawLine(Context context) {
super(context);
init(context);
}
public DrawLine(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DrawLine(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public void init(Context context) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.MITER);
paint.setColor(currentColor);
paint.setStrokeWidth(5f);
}
#Override
public void changeColor(int color) {
currentColor = color;
path = new Path();
}
#Override
public void changeWidth(float width) {
this.width = width;
path = new Path();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < paths.size(); i++) {
paint.setStrokeWidth(widthSize.get(i));
paint.setColor(color.get(i));
canvas.drawPath(paths.get(i), paint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = event.getX();
y = event.getY();
path.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
x = event.getX();
y = event.getY();
path.lineTo(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
color.add(currentColor);
paths.add(path);
widthSize.add(width);
break;
default:
return false;
}
invalidate();
return true;
}
}
you need to call invalidate() inside your custom view whenever you are modifying something related to UI. so updated methods will be:
#Override
public void changeColor(int color) {
paint.setColor(color);
invalidate();
}
#Override
public void changeWidth(float width) {
paint.setStrokeWidth(width);
invalidate();
}
I am trying to create a schedule app where if you swipe left or right, the schedule for the next and previous day will be displayed.
The swiping works, however I want to implement a feature where if you double tap or long press anywhere on the screen in the MainActivity, it opens up another activity.
My problem is that I cannot implement the double tap/long press along with the swiping. (I tried removing the onDown method of my OnSwipeTouchListener and adding an OnLongPressListener, but what ended up happening was that a swipe would also trigger the long press. Any help would be appreciated!!
main.xml for MainActivity
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainlayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ScrollView
android:id="#+id/scroller"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:layout_centerInParent="true" >
<LinearLayout
android:id="#+id/root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:animateLayoutChanges="false" >
</LinearLayout>
</ScrollView>
OnSwipeTouchListener.java
import android.content.Context;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
public class OnSwipeTouchListener implements OnTouchListener {
protected final GestureDetector gestureDetector;
public OnSwipeTouchListener (Context ctx){
gestureDetector = new GestureDetector(ctx, new GestureListener());
}
private final class GestureListener extends SimpleOnGestureListener {
private static final int SWIPE_THRESHOLD = 100;
private static final int SWIPE_VELOCITY_THRESHOLD = 100;
#Override
public boolean onDown(MotionEvent e) {
return true;
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
boolean result = false;
try {
float diffY = e2.getY() - e1.getY();
float diffX = e2.getX() - e1.getX();
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) {
onSwipeRight();
} else {
onSwipeLeft();
}
}
result = true;
}
else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
if (diffY > 0) {
onSwipeBottom();
} else {
onSwipeTop();
}
}
result = true;
} catch (Exception exception) {
exception.printStackTrace();
}
return result;
}
}
public void onSwipeRight() {
}
public void onSwipeLeft() {
}
#Override
public boolean onTouch(View arg0, MotionEvent arg1) {
// TODO Auto-generated method stub
return false;
}
}
Parts of MainActivity.java where I use OnSwipeTouchListener
// Setup stuff
LinearLayout layoutV = (LinearLayout) findViewById(R.id.root);
llV = layoutV;
ScrollView scroll = (ScrollView) findViewById(R.id.scroller);
scroller = scroll;
RelativeLayout rlayout = (RelativeLayout) findViewById(R.id.mainlayout);
rlayout.setOnTouchListener(new OnSwipeTouchListener(getApplicationContext()) {
public void onSwipeRight() {
switchDays(-1);
}
public void onSwipeLeft() {
switchDays(1);
}
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
});
scroller.setOnTouchListener(new OnSwipeTouchListener(getApplicationContext()) {
public void onSwipeRight() {
switchDays(-1);
}
public void onSwipeLeft() {
switchDays(1);
}
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
});
llV.setOnTouchListener(new OnSwipeTouchListener(getApplicationContext()) {
public void onSwipeRight() {
switchDays(-1);
}
public void onSwipeLeft() {
switchDays(1);
}
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
});
activity_test_sliding_menu.xml
<com.hpc.cbs.myapplication.FlyOutContainer xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#444488"
android:orientation="vertical"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="toggleMenu"
android:text="Button 1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="toggleMenu"
android:text="Button 1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="toggleMenu"
android:text="Button 1"/>
</LinearLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#888888"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="bla bla"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="toggleMenu"
android:text="toggle"/>
</LinearLayout>
</com.hpc.cbs.myapplication.FlyOutContainer>
and here is class
FlyOutContainer.class
public class FlyOutContainer extends LinearLayout {
private View menu;
private View content;
//Constants
protected static final int menuMargin = 150;
protected enum MenuState {
CLOSED, OPEN, CLOSING, OPENING
};
//position information attributes
protected int currentContentOffset = 0;
protected MenuState menuCurrentState = MenuState.CLOSED;
//Animation objects
protected Scroller menuAnimationScroller = new Scroller(this.getContext(), new LinearInterpolator());
protected Runnable menuAnimationRunnable = new AnimationRunnable();
protected Handler menuAnimationHandler = new Handler();
//animation constants
private static final int menuAnimationDuration = 1000;
private static final int menuAnimationPollingInterval = 16;
public FlyOutContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public FlyOutContainer(Context context){
super(context);
}
protected void onAttachedToWindow(){
super.onAttachedToWindow();
this.menu = this.getChildAt(0);
this.content = this.getChildAt(1);
this.menu.setVisibility(View.GONE);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
if(changed)
this.calculateChildDimension();
this.menu.layout(left, top, right-menuMargin, bottom);
this.content.layout(left + this.currentContentOffset, top, right + this.currentContentOffset, bottom);
}
public void toggleMenu() {
switch (this.menuCurrentState) {
case CLOSED:
this.menu.setVisibility(View.VISIBLE);
this.currentContentOffset = this.getMenuWidth();
this.content.offsetLeftAndRight(currentContentOffset);
this.menuCurrentState = MenuState.OPEN;
break;
case OPEN:
this.menu.setVisibility(View.GONE);
this.currentContentOffset = 0;
this.content.offsetLeftAndRight(-currentContentOffset);
this.menuCurrentState = MenuState.CLOSED;
break;
default:
return;
}
this.menuAnimationHandler .postDelayed(this.menuAnimationRunnable, menuAnimationPollingInterval);
}
private int getMenuWidth() {
return this.menu.getLayoutParams().width;
}
private void calculateChildDimension(){
this.content.getLayoutParams().height = this.getHeight();
this.content.getLayoutParams().width = this.getWidth();
this.menu.getLayoutParams().width = this.getWidth() - menuMargin;
this.menu.getLayoutParams().height = this.getHeight();
}
private void adjustContentPosition(boolean isAnimationOngoing)
{
int scrollerOffset = this.menuAnimationScroller.getCurrX();
this.content.offsetLeftAndRight(scrollerOffset - this.currentContentOffset);
this.currentContentOffset = scrollerOffset;
this.invalidate();
if(isAnimationOngoing)
this.menuAnimationHandler.postDelayed(this.menuAnimationRunnable, menuAnimationPollingInterval);
else
this.onMenuTransitionComplete();
}
private void onMenuTransitionComplete()
{
switch (this.menuCurrentState)
{
case OPENING:
this.menuCurrentState = MenuState.OPEN;
break;
case CLOSING:
this.menuCurrentState = MenuState.CLOSED;
this.menu.setVisibility(View.GONE);
break;
default:
return;
}
}
protected class SmoothInterpolator implements Interpolator {
#Override
public float getInterpolation(float t)
{
return (float) Math.pow(t-1,5) + 1;
}
}
protected class AnimationRunnable implements Runnable
{
#Override
public void run()
{
boolean isAnimationOngoing = FlyOutContainer.this.menuAnimationScroller.computeScrollOffset();
FlyOutContainer.this.adjustContentPosition(isAnimationOngoing);
}
}
}
here is TestSlidingMenuActivity.java
public class TestSlidingMenuActivity extends Activity {
FlyOutContainer root;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.root = (FlyOutContainer) this.getLayoutInflater().inflate(R.layout.activity_test_sliding_menu, null);
setContentView(root);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.sample, menu);
return true;
}
public void toggleMenu(View v)
{
this.root.toggleMenu();
}
}
How can I fix this?
I am starting to think that short of OpenGL I can't drag around a simple view. Seriously, I have read through 10-15 tutorials for dragging views, and EVERY tutorial shows you how to drag a drawable WITHIN a view, or drag an image INSIDE of an imageview (by matrix transformations). But NONE of the tutorials tell you how you can drag around a simple view in a superview. Its really that simple. I have a superview, I add lets say 10 subviews. Whatever subview my finger is touching drags around. I made an app for iOS http://www.facebook.com/MakeASnowman (hits the app store any day) and am trying to build it for Android. But there is no easy way to do things that are so simple in iOS (two finger rotate, drag around views, etc). This is what I want: I have a custom view that draws an image to its canvas. I then instantiate one or more of those custom views onto a "superview". I can drag/manipulate those views (meaning im not translating the drawables within those views, but the actual views themselves).
Here is my code that has the right manipulations, except it performs them of the drawable within the view:
package com.spentaklabs.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
public class MAView extends View
{
Drawable image;
private String imgName;
private float mPosX;
private float mPosY;
private float mLastTouchX;
private float mLastTouchY;
private static final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
public MAView(Context context)
{
super(context);
}
public MAView(Context context,String imgName)
{
this(context);
this.imgName = imgName;
image = context.getResources().getDrawable(R.drawable.hatshairears0);
image.setBounds(0, 0, image.getIntrinsicWidth(), image.getIntrinsicHeight());
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
public MAView(Context context, AttributeSet attrs, int defStyle)
{
super(context,attrs,defStyle);
}
#Override
public void onDraw(Canvas canvas)
{
canvas.save();
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor);
image.draw(canvas);
canvas.restore();
}
#Override
public boolean onTouchEvent(MotionEvent ev)
{
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
{
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE:
{
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP:
{
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL:
{
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP:
{
// Extract the index of the pointer that left the touch sensor
final int pointerIndex = (action & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId)
{
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
invalidate();
return true;
}
}
}
Here is some code that would let you drag a View anywhere you want on the screen...
package com.matthieu;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.FrameLayout.LayoutParams;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import java.util.ArrayList;
import java.util.Arrays;
public class DragActivity extends Activity implements View.OnTouchListener, AdapterView.OnItemLongClickListener
{
private static final String TAG="DragActivity";
private static final int NOT_DRAGGING = 0;
private static final int DRAGGING = 1;
private int state=NOT_DRAGGING;
private ImageView draggable =null;
private int dragged_position;
float current_x, current_y;
int current_icon = R.drawable.notepad;
private ArrayList<String> names = new ArrayList<String>(Arrays.asList("Matt", "Xiaohui", "Yong", "Hunt", "Andy", "Ivy", "Guanglong", "Zeyan", "Yanxia",
"Chris", "Mark", "Matthieu"));
private ArrayList<Integer> icons = new ArrayList<Integer>(Arrays.asList( R.drawable.glasses, R.drawable.monkey, R.drawable.normal, R.drawable.smile, R.drawable.wink));
private ArrayList<Integer> matching;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setupListContent();
ListView list = (ListView) findViewById(R.id.main_list);
list.setAdapter(new DragListAdapter());
list.setOnItemLongClickListener(this);
list.setOnTouchListener(this);
ImageView image = (ImageView) findViewById(R.id.main_image);
image.setImageResource(current_icon);
}
private void setupListContent() {
matching = new ArrayList<Integer>();
for (int i=0; i<names.size(); i++) {
matching.add((int) (icons.size() * Math.random()));
}
}
#SuppressWarnings("unchecked")
private class DragListAdapter extends ArrayAdapter {
public DragListAdapter() {
super(DragActivity.this, R.layout.list_item, names);
}
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.list_item, parent, false);
}
row.setDrawingCacheEnabled(true);
TextView name = (TextView) row.findViewById(R.id.item_text);
ImageView icon = (ImageView) row.findViewById(R.id.item_icon);
name.setText(names.get(position));
icon.setImageResource(icons.get(matching.get(position)));
return row;
}
}
private boolean checkOnDropIcon(MotionEvent me) {
ImageView drop_icon = (ImageView) findViewById(R.id.main_image);
Rect icon_rect = new Rect();
drop_icon.getGlobalVisibleRect(icon_rect);
Log.d(TAG, "icon at " + icon_rect.left + "<- ->" + icon_rect.right + ", " +
icon_rect.top + " ^ v" + icon_rect.bottom);
if ((me.getRawX()<icon_rect.left) || (me.getRawX()>icon_rect.right) ||
(me.getRawY()<icon_rect.top) || (me.getRawY()>icon_rect.bottom)) {
return false;
}
else {
return true;
}
}
private void checkOnDrop(MotionEvent me) {
boolean onDropIcon = checkOnDropIcon(me);
ImageView image = (ImageView) findViewById(R.id.main_image);
if ((onDropIcon) && (current_icon==R.drawable.notepad)) {
current_icon = R.drawable.exit;
image.setImageResource(current_icon);
image.invalidate();
return;
}
if ((!onDropIcon) && (current_icon==R.drawable.exit)) {
current_icon = R.drawable.notepad;
image.setImageResource(current_icon);
image.invalidate();
return;
}
}
public boolean onTouch(View view, MotionEvent me) {
if (state == NOT_DRAGGING) {
// get the position of the touch so we know where to place the dragging item if it is a long press
current_x = me.getRawX();
current_y = me.getRawY();
return false;
}
else {
FrameLayout frame = (FrameLayout) findViewById(R.id.drag_space);
if (me.getAction()==MotionEvent.ACTION_UP) {
frame.removeAllViews();
draggable=null;
frame.setVisibility(View.GONE);
state=NOT_DRAGGING;
// check if we dropped a name
if (checkOnDropIcon(me)) {
names.remove(dragged_position);
matching.remove(dragged_position);
ListView list = (ListView) findViewById(R.id.main_list);
DragListAdapter adapter = (DragListAdapter) list.getAdapter();
adapter.notifyDataSetChanged();
}
// restore the icon
ImageView image = (ImageView) findViewById(R.id.main_image);
current_icon = R.drawable.notepad;
image.setImageResource(current_icon);
image.invalidate();
}
if (me.getAction()==MotionEvent.ACTION_MOVE) {
int frame_position[] = new int[2];
frame.getLocationOnScreen(frame_position);
draggable.setPadding(
(int) me.getRawX()-frame_position[0]-(draggable.getDrawable().getIntrinsicWidth()/2),
(int) me.getRawY()-frame_position[1]-(draggable.getDrawable().getIntrinsicHeight()/2),
0, 0);
draggable.invalidate();
checkOnDrop(me);
}
return true;
}
}
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
if (state == DRAGGING) {
Log.d(TAG, "already have an object moving... ?");
return false;
}
FrameLayout frame = (FrameLayout) findViewById(R.id.drag_space);
int frame_position[] = new int[2];
frame.getLocationOnScreen(frame_position);
// setup everything for dragging
state = DRAGGING;
dragged_position = i;
draggable = new ImageView(this);
Bitmap bm = view.getDrawingCache();
draggable.setImageBitmap(bm);
draggable.setAlpha(150);
draggable.setScaleType(ImageView.ScaleType.CENTER);
draggable.setDrawingCacheEnabled(true);
draggable.setPadding((int) current_x-frame_position[0]-(bm.getWidth()/2), (int) current_y-frame_position[1]-(bm.getHeight()/2), 0, 0);
frame.setVisibility(View.VISIBLE);
frame.addView(draggable, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
return true;
}
}
Here is the main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main_frame"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView
android:id="#+id/main_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
/>
<ImageView
android:id="#+id/main_image"
android:layout_width="fill_parent"
android:layout_height="32sp"
/>
</LinearLayout>
<FrameLayout
android:id="#+id/drag_space"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="gone"/>
</FrameLayout>
And the list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<ImageView
android:id="#+id/item_icon"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_margin="3sp"/>
<TextView
android:id="#+id/item_text"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_weight="1"/>
</LinearLayout>
Hope that helps.