简体   繁体   中英

Android property animations does not work when imageview's width uses dp units

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();
            }
        });

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM