MotionLayout: MotionScene OnClick переопределяет setOnClickListener

#android #android-motionlayout

#Android #android-motionlayout

Вопрос:

Я только начинаю играть MotionLayout . Я определил макет действия, используя MotionLayout который использует a MotionScene для скрытия и отображения представления.

MotionScene Переход выглядит следующим образом:

 <Transition
    app:constraintSetStart="@id/collapsed"
    app:constraintSetEnd="@id/expanded">

    <OnClick app:target="@id/nextButton"  />

</Transition>
  

Проблема в том, что ничего не происходит, когда я программно добавляю ClickListener к кнопке:

 nextButton.setOnClickListener {
        //do some stuff
    }
  

Этот прослушиватель полностью игнорируется, но переход (расширение / сворачивание представления) запускается при каждом нажатии. Я видел, где кто-то расширяется MotionLayout для обработки событий щелчка, но, похоже, может быть более простой способ добавить другой прослушиватель щелчка для кнопки.

Вопрос 1. Есть ли способ добавить ClickListener к цели OnClick при переходе MotionLayout?

Вопрос 2: Есть ли способ сделать переход одноразовым событием?Желаемый результат заключается в том, что если представление сворачивается при нажатии кнопки, то представление расширяется, но если оно уже развернуто, оно остается развернутым.

Наконец, я использую пространство "http://schemas.android.com/apk/res-auto" имен, и в документах четко указано, что target и mode являются атрибутами для OnClick. Но проект не будет компилироваться при использовании mode , потому что он не может быть найден в этом пространстве имен.

Вопрос 3: Правильно ли я использую пространство имен?

Комментарии:

1. Вы нашли какое-либо решение для этого? Должны ли мы опубликовать это как ошибку?

2. Я думаю, об этом также следует сообщать как об ошибке…. Я так разочарован в Google из-за этого. Они делают эту классную новую игрушку, но она в основном только демонстрационная и не предназначена для реального использования

Ответ №1:

  1. Не то, что я могу найти.
  2. Я добился успеха, используя атрибут clickAction со значением «transitionToEnd». Это сделает так, что motionlayout не сможет вернуться к startConstraintSet .
 <OnClick
      motion:targetId="@ id/rateUsButton"
      motion:clickAction="transitionToEnd"/>
  
  1. Это пространство имен, которое я использую, а также пространство имен, используемое в примерах, которые я видел.

Только что столкнулся с той же проблемой сегодня. Я смог перехватить щелчок, используя setOnTouchListener вместо setOnClickListener в моем коде.

 rateUsButton.setOnTouchListener { _, event ->
    if (event.action == MotionEvent.ACTION_UP) {
        // handle the click
    }
    false
}
  

Я знаю, что это решение не самое лучшее, но я не нашел другого варианта. Возврат false означает, что касание здесь не обрабатывалось и, следовательно, будет обрабатываться макетом движения.

Ответ №2:

Вы также можете просто программно обработать щелчок с самого начала, удалив

  <OnClick app:target="@id/nextButton"  />
  

в целом. Также легко увидеть, расширено ли ваше представление, проверив ход вашего перехода. Таким образом, вы можете программно обработать это в своем файле java / kotlin с помощью

 yourButton.setOnClickListener {
    if (yourMotionLayoutId.progress == 0.0)
        yourMotionLayoutId.transitionToEnd
}
  

Таким образом, он проверит, находится ли переход в состоянии, в котором он не произошел (прогресс будет равен 0.0) и transition, в противном случае он ничего не сделает.

Ответ №3:

Я нашел более чистый и более правильный способ сделать это, вы можете сделать это…. OnClick непосредственно из представления ..

Примечание: это не работает с: <OnSwipe/> только <OnClick/>

PD. Извините, я из Мексики, и я использую переводчик

 <androidx.appcompat.widget.AppCompatImageView
        android:id="@ id/play_pause_button_collapsed"
        android:layout_width="30dp"
        android:layout_height="50dp"
        app:srcCompat="@drawable/ic_play_arrow_black_48dp"
        android:layout_marginTop="25dp"
        android:elevation="2dp"
        android:alpha="0"

        android:onClick="handleAction"

        tools:ignore="ContentDescription" />



fun handleAction(view: View) { 
   //handle click
}
  

Ответ №4:

Я только что использовал этот хак: щелчок обрабатывается программно, но он запускает скрытое представление, для которого <OnClick> зарегистрирован в MotionScene :

 actualVisibleView.setOnClickListener {
            doSomeLogic()
            hiddenView.performClick()
        }
  

И в MotionScene :

 <Transition
        android:id="@ id/hackedTransitionThanksToGoogle"
        motion:constraintSetEnd="@layout/expanded"
        motion:constraintSetStart="@layout/closed"
        motion:duration="300"
        motion:motionInterpolator="linear">

        <OnClick
            motion:clickAction="transitionToEnd"
            motion:targetId="@ id/hiddenView" />
</Transition>
  

Ответ №5:

В общем, если вам нужен обратный вызов, вы, вероятно, захотите самостоятельно управлять анимацией. Поэтому, если вы добавляете onClick, вы должны вызвать переход самостоятельно.

 public void onClick(View v) {
   ((MotionLayout)v.getParent()).transitionToEnd());
   // you can decide all the actions and conditionals.
 }
  

Намерение было полезно, что разработчику все равно. скрыть / показать элементы пользовательского интерфейса и т. Д. Или для тестирования, прежде чем приступить к подключению обратных вызовов.

Ответ №6:

Вы можете реализовать MotionLayout.TransitionListener к событию обработчика при переходе.

 public class LoginActivity extends AppCompatActivity implements MotionLayout.TransitionListener {
private static final String TAG = "LoginActivity";
private FirebaseAuth mAuth;
private LoginLayoutBinding binding;

@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = LoginLayoutBinding.inflate(getLayoutInflater());
    setContentView(binding.getRoot());

    // initialize the FirebaseAuth instance.
    mAuth = FirebaseAuth.getInstance();
    binding.getRoot().addTransitionListener(this);
}


@Override
public void onStart() {
    super.onStart();
    // Check if user is signed in (non-null) and update UI accordingly.
    FirebaseUser currentUser = mAuth.getCurrentUser();
    updateUI(currentUser);
}

private void updateUI(FirebaseUser currentUser) {
    hideProgressBar();
    if (currentUser != null) {
        Intent intent = new Intent(LoginActivity.this, MainActivity.class);
        startActivity(intent);
        finish();
    }
}

private void hideProgressBar() {
    binding.progressBar2.setVisibility(View.GONE);
}

private void createAccount(String email, String password) {
    mAuth.createUserWithEmailAndPassword(email, password)
            .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        // Sign in success, update UI with the signed-in user's information
                        Log.d(TAG, "createUserWithEmail:success");
                        FirebaseUser user = mAuth.getCurrentUser();
                        updateUI(user);
                    } else {
                        // If sign in fails, display a message to the user.
                        Log.w(TAG, "createUserWithEmail:failure", task.getException());
                        Toast.makeText(LoginActivity.this, "Authentication failed.",
                                Toast.LENGTH_SHORT).show();
                        updateUI(null);
                    }
                }
            });
}

private void signIn(String email, String password) {
    mAuth.signInWithEmailAndPassword(email, password)
            .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        // Sign in success, update UI with the signed-in user's information
                        Log.d(TAG, "signInWithEmail:success");
                        FirebaseUser user = mAuth.getCurrentUser();
                        updateUI(user);
                    } else {
                        // If sign in fails, display a message to the user.
                        Log.w(TAG, "signInWithEmail:failure", task.getException());
                        Toast.makeText(LoginActivity.this, "Authentication failed.",
                                Toast.LENGTH_SHORT).show();
                        updateUI(null);
                    }
                }
            });
}


@Override
public void onTransitionStarted(MotionLayout motionLayout, int startId, int endId) {

}

@Override
public void onTransitionChange(MotionLayout motionLayout, int startId, int endId, float progress) {

}

@Override
public void onTransitionCompleted(MotionLayout motionLayout, int currentId) {
    if (currentId==R.id.end){
        binding.btnLogin.setText(R.string.sign_up);
        binding.textView3.setEnabled(false);
        binding.textView2.setEnabled(true);
    }else {
        binding.btnLogin.setText(R.string.login);
        binding.textView2.setEnabled(false);
        binding.textView3.setEnabled(true);
    }

}

@Override
public void onTransitionTrigger(MotionLayout motionLayout, int triggerId, boolean positive, float progress) {

}
  

}

Ответ №7:

Вот простое решение:

Просто добавьте это удовольствие:

  @SuppressLint("ClickableViewAccessibility")
fun View.setOnClick(clickEvent: () -> Unit) {
    this.setOnTouchListener { _, event ->
        if (event.action == MotionEvent.ACTION_UP) {
            clickEvent.invoke()
        }
        false
    }
}
  

Вот как вы его используете:

 nextButton.setOnClick {
        //Do something 
}
  

Ответ №8:

Самым простым решением, на мой взгляд, является MotionLayout расширение

Вы можете создавать свои собственные пользовательские MotionLayout и обрабатывать касания с некоторым порогом. Например:

 import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.motion.widget.MotionLayout;

public class CustomMotionLayout extends MotionLayout {

    private final static int CLICK_ACTION_THRESHOLD = 5;
    private float startX;
    private float startY;

    public CustomMotionLayout(@NonNull Context context) {
        super(context);
    }

    public CustomMotionLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomMotionLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = event.getX();
                startY = event.getY();
                break;
            case MotionEvent.ACTION_UP:
                float endX = event.getX();
                float endY = event.getY();
                if (isAClick(startX, endX, startY, endY)) {
                    // here we pass the MotionEvent
                    // to the next touch listener, bypassing 
                    // the MotionLayout touch event handler
                    return false;
                }
                break;
        }
        return super.onTouchEvent(event);
    }

    private boolean isAClick(float startX, float endX, float startY, float endY) {
        float differenceX = Math.abs(startX - endX);
        float differenceY = Math.abs(startY - endY);
        return !(differenceX > CLICK_ACTION_THRESHOLD || differenceY > CLICK_ACTION_THRESHOLD);
    }

}
  

Затем вы можете обработать щелчок по элементу следующим образом:

 binding.carousel.setAdapter(new Carousel.Adapter() {
    @Override
    public int count() {
        return images.length;
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public void populate(View view, int index) {
        if (view instanceof ImageView) {
            ImageView imageView = (ImageView) view;
            imageView.setImageResource(images[index]);
            imageView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent motionEvent) {
                    Log.d("CLICK", "click"); // <-- click
                    return false;
                }
            });
        }
    }

    @Override
    public void onNewItem(int index) {}
});
  

Это не идеальная реализация, просто пример, но она не мешает анимированной карусели и обрабатывает клики

Ответ №9:

Вчера у меня возникла проблема, и только сейчас она решена, и все, что я делаю, это просто добавляю View тег внутри MotionLayout тега, а затем присваиваю ему onClick атрибут.

вот предварительный просмотр

 <androidx.constraintlayout.motion.widget.MotionLayout
    android:id="@ id/buttonMotion"
    android:layout_width="70dp"
    android:layout_height="70dp"
    android:layout_gravity="center"
    app:layoutDescription="@xml/circle_to_square">
    <androidx.constraintlayout.utils.widget.ImageFilterView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@ id/puthPic"
        android:scaleType="centerCrop"
        android:src="@drawable/red"
        android:onClick="toggleBroadcasting"
        />
    <View
        android:id="@ id/puthPicView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:onClick="toggleBroadcasting"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
  

Вы можете увидеть мою суть здесь для получения дополнительной информации.