#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:
- Не то, что я могу найти.
- Я добился успеха, используя атрибут clickAction со значением «transitionToEnd». Это сделает так, что motionlayout не сможет вернуться к startConstraintSet .
<OnClick
motion:targetId="@ id/rateUsButton"
motion:clickAction="transitionToEnd"/>
- Это пространство имен, которое я использую, а также пространство имен, используемое в примерах, которые я видел.
Только что столкнулся с той же проблемой сегодня. Я смог перехватить щелчок, используя 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>
Вы можете увидеть мою суть здесь для получения дополнительной информации.