如何在Android中将RepeatListener与平滑图像旋转结合起来

huangapple go评论84阅读模式
英文:

How to combine a RepeatListener in Android with a smooth image rotation

问题

我有一个带有ImageView(风扇)和操作按钮的Fragment。按下操作按钮时,应使用来自 https://stackoverflow.com/questions/1634252/how-to-make-a-smooth-image-rotation-in-android 的动画方法旋转ImageView。以下是布局的屏幕截图:

如何在Android中将RepeatListener与平滑图像旋转结合起来

实际上,当我只按下按钮一次然后释放时,这个功能运行得很好。然而,当有一个RepeatListener并按住它时,会出现两个问题:

  1. binding.fan.startAnimation(rotate); 在短时间内被调用多次。这会导致将ImageView还原,以至于似乎在按钮不再被按下之前不会再次进行动画处理。
  2. 当按钮不再被按下时,ImageView应该保持在按钮被释放时的位置。因此,它不应该返回到原始状态。

你能想到解决这个问题的方法吗?

以下是带有RepeatListener的Fragment的Java代码:

package com.example.game;

import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import androidx.fragment.app.Fragment;
import com.example.game.databinding.FragmentTest2Binding;

public class Test2 extends Fragment implements View.OnClickListener {
    private FragmentTest2Binding binding;
    int widthDisplay;
    int heightDisplay;

    public Test2() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        binding = FragmentTest2Binding.inflate(inflater, container, false);

        WindowManager wm = (WindowManager) getActivity().getWindowManager();
        Display display = wm.getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        widthDisplay = size.x;
        heightDisplay = size.y;
        container.getContext();

        // 定义旋转动画
        RotateAnimation rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        rotate.setDuration(1000);
        rotate.setInterpolator(new LinearInterpolator());
        rotate.setFillAfter(true);
        binding.fan.setAnimation(rotate);

        // 定义并注册RepeatListener在操作按钮上
        binding.buttonAction.setOnTouchListener(new RepeatListener(30, 30, new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                binding.fan.startAnimation(rotate);
                // 需要重复执行的代码
            }
        }));

        return binding.getRoot();
    }

    @Override
    public void onClick(View v) {
        // 点击事件
    }
}

这是XML布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:background="@drawable/game_test_background_hp_fan"
    tools:context=".MainActivity"
    android:id="@+id/constraintLayout">

    <Button
        android:id="@+id/button_action"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.102"
        app:layout_constraintHorizontal_bias="0.373"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.745"
        app:layout_constraintWidth_percent="0.12" />

    <ImageView
        android:id="@+id/fan"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_percent="0.09"
        app:layout_constraintHorizontal_bias="0.539"
        app:layout_constraintVertical_bias="0.51"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/heat_pump_fan" />
</androidx.constraintlayout.widget.ConstraintLayout>

请注意,你需要确保RepeatListener类已正确导入并可用。希望这可以帮助你解决问题。

英文:

I have a Fragment with a ImageView (Fan) and an action button. When pressing the action button the ImageView should rotate by using the animation approach from https://stackoverflow.com/questions/1634252/how-to-make-a-smooth-image-rotation-in-android. Here is the screenshot of the layout:
如何在Android中将RepeatListener与平滑图像旋转结合起来

Actually this works fine when I just press the button once and release it. However, when having a RepeatListener and pressing and holding it there are 2 problems:

  1. The binding.fan.startAnimation(rotate); is called several times within a short time. This leads to setting back the ImageView such that it does not seem to animate until the button is not pressed any more
  2. When the button is not pressed any more, the ImageView should stay at the very position that it has had when the button was released. So it should not turn back to its original state.

Can you think of a approach to archieve this?

Here is the Java code of the Fragment with the RepeatListener

package com.example.game;
import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import androidx.fragment.app.Fragment;
import com.example.game.databinding.FragmentTest2Binding;
public class Test2 extends Fragment implements View.OnClickListener {
private FragmentTest2Binding binding;
int widthDisplay;
int heightDisplay;
public Test2() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentTest2Binding.inflate(inflater, container, false);
WindowManager wm = (WindowManager) getActivity().getWindowManager();
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
widthDisplay = size.x;
heightDisplay = size.y;
container.getContext();
//Define the rotation animation
RotateAnimation rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(1000);
rotate.setInterpolator(new LinearInterpolator());
rotate.setFillAfter(true);
binding.fan.setAnimation(rotate);
// Define and registrate RepeatListener on the action button
binding.buttonAction.setOnTouchListener((View.OnTouchListener) new RepeatListener(30, 30, new View.OnClickListener() {
@Override
public void onClick(View view) {
binding.fan.startAnimation(rotate);
// the code to execute repeatedly
}
}));
return binding.getRoot();
}
@Override
public void onClick(View v) {
}
}

This is the XML layout file:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
xmlns:tools=&quot;http://schemas.android.com/tools&quot;
android:layout_width=&quot;match_parent&quot;
android:layout_height=&quot;match_parent&quot;
android:background=&quot;@drawable/game_test_background_hp_fan&quot;
tools:context=&quot;.MainActivity&quot;
android:id=&quot;@+id/constraintLayout&quot;&gt;
&lt;Button
android:id=&quot;@+id/button_action&quot;
android:layout_width=&quot;0dp&quot;
android:layout_height=&quot;0dp&quot;
android:text=&quot;Button&quot;
app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
app:layout_constraintEnd_toEndOf=&quot;parent&quot;
app:layout_constraintHeight_percent=&quot;0.102&quot;
app:layout_constraintHorizontal_bias=&quot;0.373&quot;
app:layout_constraintStart_toStartOf=&quot;parent&quot;
app:layout_constraintTop_toTopOf=&quot;parent&quot;
app:layout_constraintVertical_bias=&quot;0.745&quot;
app:layout_constraintWidth_percent=&quot;0.12&quot; /&gt;
&lt;ImageView
android:id=&quot;@+id/fan&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;0dp&quot;
app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
app:layout_constraintEnd_toEndOf=&quot;parent&quot;
app:layout_constraintHeight_percent=&quot;0.09&quot;
app:layout_constraintHorizontal_bias=&quot;0.539&quot;
app:layout_constraintVertical_bias= &quot;0.51&quot;
app:layout_constraintStart_toStartOf=&quot;parent&quot;
app:layout_constraintTop_toTopOf=&quot;parent&quot;
app:srcCompat=&quot;@drawable/heat_pump_fan&quot; /&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;

Edit: Here is the java class of the RepeatListener:

import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
/**
* A class, that can be used as a TouchListener on any view (e.g. a Button).
* It cyclically runs a clickListener, emulating keyboard-like behaviour. First
* click is fired immediately, next one after the initialInterval, and subsequent
* ones after the normalInterval.
*
* &lt;p&gt;Interval is scheduled after the onClick completes, so it has to run fast.
* If it runs slow, it does not generate skipped onClicks. Can be rewritten to
* achieve this.
*/
public class RepeatListener implements View.OnTouchListener {
private Handler handler = new Handler();
private int initialInterval;
private final int normalInterval;
private final View.OnClickListener clickListener;
private View touchedView;
private Runnable handlerRunnable = new Runnable() {
@Override
public void run() {
if(touchedView.isEnabled()) {
handler.postDelayed(this, normalInterval);
clickListener.onClick(touchedView);
} else {
// if the view was disabled by the clickListener, remove the callback
handler.removeCallbacks(handlerRunnable);
touchedView.setPressed(false);
touchedView = null;
}
}
};
/**
* @param initialInterval The interval after first click event
* @param normalInterval The interval after second and subsequent click
*       events
* @param clickListener The OnClickListener, that will be called
*       periodically
*/
public RepeatListener(int initialInterval, int normalInterval,
View.OnClickListener clickListener) {
if (clickListener == null)
throw new IllegalArgumentException(&quot;null runnable&quot;);
if (initialInterval &lt; 0 || normalInterval &lt; 0)
throw new IllegalArgumentException(&quot;negative interval&quot;);
this.initialInterval = initialInterval;
this.normalInterval = normalInterval;
this.clickListener = clickListener;
}
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.removeCallbacks(handlerRunnable);
handler.postDelayed(handlerRunnable, initialInterval);
touchedView = view;
touchedView.setPressed(true);
clickListener.onClick(view);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
handler.removeCallbacks(handlerRunnable);
touchedView.setPressed(false);
touchedView = null;
return true;
}
return false;
}
}

Reminder: Does anyone kow how to do this?

答案1

得分: 1

The intend of your linked post is to rotate a view forever but what you want is a custom rotation. You can rotate any view using setRotation(float angle). Make sure that the view has free space for rotating.

public class Test2 extends Fragment implements View.OnClickListener {

float rotationAngle = 0; // &lt;------- added
...

...

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        binding = FragmentTest2Binding.inflate(inflater, container, false);

        // Define and registrate RepeatListener on the action button


        binding.buttonAction.setOnTouchListener(new RepeatListener(30, 30, view -&gt; {
            rotationAngle += 5;
            binding.fan.setRotation(-rotationAngle); // I want to rotate counter-clock-wise

        }));


        return binding.getRoot();

    }
}

如何在Android中将RepeatListener与平滑图像旋转结合起来

英文:

The intend of
your linked post

https://stackoverflow.com/q/1634252/6576302

is to rotate a view forever but what you want is a custom rotation. You can rotate any view using setRotation(float angle). Make sure that the view has free space for rotating.

public class Test2 extends Fragment implements View.OnClickListener {
float rotationAngle = 0; // &lt;------- added
...
...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentTest2Binding.inflate(inflater, container, false);
// Define and registrate RepeatListener on the action button
binding.buttonAction.setOnTouchListener(new RepeatListener(30, 30, view -&gt; {
rotationAngle += 5;
binding.fan.setRotation(-rotationAngle); // I want to rotate counter-clock-wise
}));
return binding.getRoot();
}
}

如何在Android中将RepeatListener与平滑图像旋转结合起来

huangapple
  • 本文由 发表于 2023年6月8日 18:41:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/76431011.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定