Trong quá trình học về các thành phần mới của thiết kế Material Design cho Android, tôi đã tìm hiểu về cách sử dụng CoordinatorLayout kết hợp với các thành phần khác để tạo ra các hiệu ứng. Trong bài viết này, chúng ta sẽ tập trung vào FloatingActionButton và cách nó tương tác với Snackbar.
Đầu tiên, hãy xem xét layout XML và mã Java:
Mã trên rất đơn giản và tạo ra hiệu ứng như sau: khi Snackbar xuất hiện, FloatingActionButton sẽ di chuyển lên hoặc xuống để tránh che lấp Snackbar. Hiệu ứng này chỉ xảy ra khi FloatingActionButton được đặt trong CoordinatorLayout và sử dụng cùng với Snackbar.
FloatingActionButton kế thừa từ ImageButton, nhưng nếu thay thế FloatingActionButton bằng ImageButton hoặc ImageView, hiệu ứng sẽ không còn. Điều này là do FloatingActionButton có một hành vi (behavior) được định nghĩa thông qua phản chiếu và chú thích. Hành vi này được định nghĩa trong lớp nội bộ Behavior, kế thừa từ CoordinatorLayout.Behavior. Dưới đây là hai phương thức quan trọng:
Phương thức `layoutDependsOn` kiểm tra xem view phụ thuộc có phải là Snackbar hay không. Nếu đúng, FloatingActionButton sẽ di chuyển theo. Phương thức `onDependentViewChanged` được gọi liên tục khi Snackbar di chuyển. Phương thức `getFabTranslationYForSnackbar` tính toán độ dịch chuyển của FloatingActionButton dựa trên vị trí của Snackbar.
Bây giờ, chúng ta sẽ tạo một lớp tự định nghĩa MyBehavior kế thừa từ CoordinatorLayout.Behavior để thêm hiệu ứng xoay cho FloatingActionButton khi Snackbar xuất hiện:
Sau đó, sửa đổi layout XML để sử dụng hành vi mới:
Hiệu ứng sau khi áp dụng hành vi mới:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btnSnackBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nhấn để hiển thị Snackbar"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:src="@mipmap/ic_launcher" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnSnackBar = findViewById(R.id.btnSnackBar);
btnSnackBar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v, "Đây là một Snackbar", Snackbar.LENGTH_SHORT).show();
}
});
}
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
updateFabTranslationForSnackbar(parent, child, dependency);
} else if (dependency instanceof AppBarLayout) {
updateFabVisibility(parent, (AppBarLayout) dependency, child);
}
return false;
}
private float getFabTranslationYForSnackbar(CoordinatorLayout parent, FloatingActionButton fab) {
float minOffset = 0;
List<View> dependencies = parent.getDependencies(fab);
for (int i = 0, z = dependencies.size(); i < z; i++) {
View view = dependencies.get(i);
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - view.getHeight());
}
}
return minOffset;
}
public class MyBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
public MyBehavior() {
}
public MyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
float fTransY = getFabTranslationYForSnackbar(parent, child);
float iRate = -fTransY / dependency.getHeight();
child.setRotation(iRate * 90);
child.setTranslationY(fTransY);
return false;
}
private float getFabTranslationYForSnackbar(CoordinatorLayout parent, FloatingActionButton fab) {
float minOffset = 0;
List<View> dependencies = parent.getDependencies(fab);
for (int i = 0, z = dependencies.size(); i < z; i++) {
View view = dependencies.get(i);
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - view.getHeight());
}
}
return minOffset;
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Button
android:id="@+id/btnSnackBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nhấn để hiển thị Snackbar"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
app:layout_behavior="com.example.joy.coordinatorlayouttest.MyBehavior"
android:src="@mipmap/ic_launcher" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>