Reputation: 1827
I am creating a game, where there will be a lot of clickable ImageView(s), and one unclickable imageView, when the clickable ImageView is clicked, it should swap its position with the unclickable ImageView (in this example, its called imageView2).
Everything works fine when I am using px unit for my imageView(s). However, as you know using px units will resulted in different display on different devices. However, the property animations does not work when I use dp units. How do I get it working with dp units ??
MainActivity.java
public class MainActivity extends Activity implements View.OnClickListener {
ImageView imageView1, imageView2;
float x, x_9, y, y_9;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView1 = (ImageView) findViewById(R.id.imageView1);
imageView2 = (ImageView) findViewById(R.id.imageView2);
imageView1.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// Get x position of clicked imageView
x = v.getX();
// Get x position of imageView2
x_9 = imageView2.getX();
// Get y position of clicked imageView
y = v.getY();
// Get y position of imageView2
y_9 = imageView2.getY();
// Check if imageViews are align with each other either horizontally or vertically
if((x == x_9 && y + 100 == y_9) || (x == x_9 && y - 100 == y_9) || (x + 100 == x_9 && y == y_9) || (x - 100 == x_9 && y == y_9)) {
// If they are aligned, swap their position with property animation
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("translationX", x_9);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("translationY", y_9);
PropertyValuesHolder pvhX9 = PropertyValuesHolder.ofFloat("translationX", x);
PropertyValuesHolder pvhY9 = PropertyValuesHolder.ofFloat("translationY", y);
ObjectAnimator blockAnim = ObjectAnimator.ofPropertyValuesHolder(v, pvhX, pvhY);
blockAnim.setDuration(500);
blockAnim.setRepeatCount(0);
blockAnim.start();
ObjectAnimator blockAnim2 = ObjectAnimator.ofPropertyValuesHolder(imageView2, pvhX9, pvhY9);
blockAnim2.setDuration(500);
blockAnim2.setRepeatCount(0);
blockAnim2.start();
}
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<RelativeLayout
android:id="@+id/relative"
android:background="@color/grey"
android:layout_width="400dp"
android:layout_height="400dp"
>
<ImageView
android:id="@+id/imageView1"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/smiley1"/>
<ImageView
android:id="@+id/imageView2"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/smiley2"
android:translationX="100dp"/>
</RelativeLayout>
</RelativeLayout>
How do I work around this problem ? All suggestions are greatly welcomed. Thank you.
As suggested by Nikola Milutinovic, I added GlobalLayoutListener like below
this.findViewById(R.id.relative).getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
x = v.getX();
x_9 = imageView2.getX();
y = v.getY();
y_9 = imageView2.getY();
}
});
Upvotes: 0
Views: 901
Reputation: 742
See documentation for these two methods below and find which one suits you the most. container
would be your root layout (this RelativeLayout at the top or this with id relative
)
container.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
}
});
container.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
@Override
public void onDraw() {
}
});
Here is the example of my implementation of this listener. I do all sorts of stuff inside. Please take a look.
public class CustomOnGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
private Context context;
private View fragment;
private float x1;
private float y1;
private float x2;
private float y2;
public CustomOnGlobalLayoutListener(Context context, View view, float x1, float y1, float x2, float y2) {
this.context = context;
this.fragment = view;
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
@Override
public void onGlobalLayout() {
fragment.setX(x1);
fragment.setY(y1);
removeOnGlobalLayoutListener();
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) fragment.getLayoutParams();
fragment.setLayoutParams(layoutParams);
fragment.setPivotX(0);
fragment.setPivotY(0);
fragment.setScaleX((x2 - x1) / fragment.getMeasuredWidth());
fragment.setScaleY((y2 - y1) / fragment.getMeasuredHeight());
fragment.setAlpha(0f);
PropertyValuesHolder pA = PropertyValuesHolder.ofFloat(View.ALPHA, 1f);
PropertyValuesHolder pX = PropertyValuesHolder.ofFloat(View.X, 0f);
PropertyValuesHolder pY = PropertyValuesHolder.ofFloat(View.Y, 0f);
PropertyValuesHolder pSx = PropertyValuesHolder.ofFloat(View.SCALE_X, 1);
PropertyValuesHolder pSy = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1);
final ObjectAnimator animator1 = ObjectAnimator.ofPropertyValuesHolder(fragment, pA, pX, pY, pSx, pSy);
animator1.setInterpolator(new AccelerateDecelerateInterpolator());
animator1.setDuration(200);
animator1.start();
}
private void removeOnGlobalLayoutListener() {
if (Build.VERSION.SDK_INT < 16) {
//noinspection deprecation
fragment.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
fragment.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
}
DO NOT FORGET TO REMOVE GLOBAL LAYOUT LISTENER;
If you have any problem implementing this, write and we will think something out :)
You can also check my github repo where I am actually using this to achieve opening fragment with viewpager from point of touch.
https://github.com/mrnmilutinovic/HumanityTestProject
Check ImagePagerFragment
class to see it's usage.
This code is compilable, so you can also run and see that it works.
Upvotes: 0
Reputation:
I think the way to do it is to change your hard coded referece to the width and height by the actual value contained by the imageViews. In your if condition change 100 to getHeight() or getWidth();
int height = imageView1.getHeight();
int width = imageView1.getWidth();
if((x == x_9 && y + height == y_9) || (x == x_9 && y - height == y_9) ||
(x + width == x_9 && y == y_9) || (x - width == x_9 && y == y_9)){
}
This way you do not have a reference to px. The "100" value that your were using meant 100px. The real width or height for an hdpi device would of been something like 1.5*100 = 150px. By using getWidth() and getHeight(), you are using the actual value of px in your screen. I hope it is clear. You will need to modify your if condition for more than 2 imageViews, but the principle is the same.
Finally it is better to use AnimatorSet if you want your animations to be synchronized.
ArrayList <Animator> animations = new ArrayList<Animator>();
animations.add(ObjectAnimator.ofPropertyValuesHolder(v, pvhX, pvhY));
animations.add(ObjectAnimator.ofPropertyValuesHolder(imageView2, pvhX9, pvhY9));
AnimatorSet animatoreSet = new AnimatorSet();
animatorSet.setDuration(500);
animatorSet.setRepeatCount(0);
animatorSet.playTogether(animations);
animatorSet.start();
By doing this if you need to add listeners you just add them once.
Upvotes: 1