Thứ Sáu, 9 tháng 3, 2018

CoordinatorLayout (2) ứng dụng một số loại View

Phần này là tiếp theo của bài viết CoordinatorLayout (1) xây dựng bố cục, Behavior và Nested Scroll, trình bày thêm một số đối tượng View trong CoordinatorLayout như FAB, ActionBar, AppBarLayout, CollapsingToolbarLayout và đặc biệt là BottomSheet

FAB và Snackbar trong CoordinatorLayout

Về FAB (Floating Action Buttuon) và Snackbar đã trình bày chi tiết cách sử dụng tại: FAB vả Snackbar. Phần này nói thêm về hai thành phần này khi nằm trong CooordinatorLayout
FAB khi đặt trong CoordinatorLayout nó có sẵn Behavior là FloatingActionButton.Behavior, tương tự khi tạo Snackbar nó có Behavior là BottomSheetBehavior. Khi Snackbar hiện thị, ứng xử của 2 đối tượng này phối hợp với nhau là: Nếu Snackbar chiếm chỗ của FAB, FAB sẽ dịch chuyển lên và ngược lại khi Snackbar ẩn đi FAB sẽ quay lại vị trí cũ.
Trở lại code ví dụ phần 1, thêm FAB vào res\layout\activity_coordinator.xml
<android.support.design.widget.FloatingActionButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|right"
    android:layout_margin="16dp"
    android:src="@drawable/iconfab"
    android:tint="#d6bed1"
    app:layout_anchor="@id/mylistview_2"
    app:layout_anchorGravity="bottom|right|end"/>
Trong OnCreate thêm đoạn mã để chi bấm vào TextView có ID textTest thị tạo một Snackbar
findViewById(R.id.txtTest).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Snackbar snackbar = Snackbar.make(view,
            ((TextView)view).getText(), Snackbar.LENGTH_SHORT);
        snackbar.show();
    }
});
Kết quả:

Khi Snackbar xuất hiện thì FAB đã dịch chuyển lên trên

Cuộn Toolbar / ActionBar trong CoordinatorLayout

Trước tiên bạn đọc về: Sử dụng Toolbar, ActionBar trong lập trình Android, phân này giải thích thêm một số chi tiết.
Ở đây ta tiến hành thay đổi ứng dụng trên để khi cuộn trong các RecylerView hoặc NestedScrollView thì tương ứng Toolbar/ActionBar cuộn lên xuống.
res\layout\activity_coordinator.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    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:fitsSystemWindows="true"
    tools:context="net.xuanthulba.coordinator.CoordinatorActivity">
    
    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:titleTextColor="@android:color/white"
            app:layout_scrollFlags="scroll|enterAlways" />
    </android.support.design.widget.AppBarLayout>


    <LinearLayout
        android:id="@+id/mainlayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#1db182"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        app:layout_anchorGravity="bottom">

        <android.support.v7.widget.RecyclerView
            android:layout_margin="10dp"
            android:padding="5dp"
            android:id="@+id/mylistview"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@android:color/holo_orange_light" />

        <android.support.v7.widget.RecyclerView
            android:layout_margin="10dp"
            android:padding="5dp"
            android:id="@+id/mylistview_2"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@android:color/holo_red_dark" />
    </LinearLayout>

    <TextView
        android:id="@+id/txtTest"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="25dp"
        android:background="@android:color/holo_green_dark"
        android:padding="10dp"
        android:text="This is a TextView in CoordinatorLayout"
        app:layout_anchor="@+id/mainlayout"
        app:layout_anchorGravity="right|top"
        app:layout_behavior="net.xuanthulba.coordinator.FirstBehavior" />


    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:layout_margin="16dp"
        android:src="@drawable/iconfab"
        android:tint="#d6bed1"
        app:layout_anchor="@id/mylistview_2"
        app:layout_anchorGravity="bottom|right|end"/>


</android.support.design.widget.CoordinatorLayout> 
Giải thích code XML trên
Sử dụng một AppBarLayout để chứa ActionBar (Toolbar) vì AppBarLayout hỗ trợ tính năng cuộn. Khi sử dụng AppBarLayout như code XML trên, ta cần thiết lập không sử dụng Toolbar mặc định bằng cách thiết lập theme mà Activity sử dụng
Ví dụ, tạo theme có thiết lập không sử dụng ActionBar hệ thống mặc đinh trong styles.xml
 <resources>
    <-- --- -->
    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>
    <-- --- -->

</resources>
Sau đó trong manifests áp theme đó cho Activity, ví dụ như:
<activity
    android:name=".CoordinatorActivity"
    android:theme="@style/AppTheme.NoActionBar">
    <!-- --- -->
</activity>
Do đã loại bỏ ActionBar mặc định, trong onCreate bạn cần có mã thiết lập Toolbar chữa trong XML sẽ là Toolbar của Activity
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
phần tử LinearLayout với ID là mainlayout được gán Behavior có giá trị: @string/appbar_scrolling_view_behavior hay chính là: android.support.design.widget.AppBarLayout$ScrollingViewBehavior, đây là Behavior điều khiển phần tử chứa nó bám theo AppBarLayout trong CoordinatorLayout. Vì thế bạn sẽ thấy, mainlayout luôn bám mép dưới của AppBarLayout khi cuộn
Bằng cách sử dụng như trên, khi có hành động cuộn trên các phần tử như RecylerView, NestedScrollView ... (tức những phần tử có phát sự kiện Nested Scroll) thì Toolbar sẽ cuộn lên.
Xem kết quả - thấy ActionBar (toolbar) và mainlayout đã trượt lên, xuống khi cuộn phần tử trong danh sách.

Bạn cần lưu ý về tham số XML: app:layout_scrollFlags (xem phần tử Toolbar). Tham số này bắt buộc phải thiết lập để có được hiệu ứng cuộn. Các giá trị nó có thể nhận
  • scroll : cuộn khi có sự kiện cuộn trong các View, tham số này phải luôn thiết lập, sau đó mới thêm các thiết lập khác như sau
  • enterAlways: sẽ cuộn cho bất kỳ sự kiện cuộn nào của View (cuộn ngay xuống kể cả cuộn xuống của View chưa đến đầu phần tử)
  • enterAlwaysCollapsed: bao giờ cũng phải sử dụng cùng enterAlways.
  • exitUntilCollapsed: View sẽ thu gọn khi vuốt ngược lên, quá trình diễn ra cho tới giá trị minHeight

CollapsingToolbarLayout trong CoordinatorLayout

Áp dụng thêm CollapsingToolbarLayout, để có các hiệu ứng Expand/Collapse khi thao tác cuộn diễn ra trong Coordinator.
res\layout\activity_coordinator.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    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:fitsSystemWindows="false"
    tools:context="net.xuanthulba.coordinator.CoordinatorActivity">


    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/AppTheme.AppBarOverlay"

        android:layout_height="wrap_content">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:toolbarId="@+id/toolbar"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:id="@+id/image"
                android:layout_width="match_parent"
                android:layout_height="180dp"
                android:scaleType="centerCrop"
                android:src="@drawable/forest"
                app:layout_collapseMode="parallax"/>


            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:layout_scrollFlags="scroll|enterAlways"
                app:titleTextColor="@android:color/white" />


        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>


    <LinearLayout
        android:id="@+id/mainlayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#1db182"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        app:layout_anchorGravity="bottom">

        <android.support.v7.widget.RecyclerView
            android:layout_margin="10dp"
            android:padding="5dp"
            android:id="@+id/mylistview"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@android:color/holo_orange_light" />

        <android.support.v7.widget.RecyclerView
            android:layout_margin="10dp"
            android:padding="5dp"
            android:id="@+id/mylistview_2"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@android:color/holo_red_dark" />
    </LinearLayout>

    <TextView
        android:id="@+id/txtTest"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="25dp"
        android:background="@android:color/holo_green_dark"
        android:padding="10dp"
        android:text="This is a TextView in CoordinatorLayout"
        app:layout_anchor="@+id/mainlayout"
        app:layout_anchorGravity="right|top"
        app:layout_behavior="net.xuanthulba.coordinator.FirstBehavior" />


    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:layout_margin="16dp"
        android:src="@drawable/iconfab"
        android:tint="#d6bed1"
        app:layout_anchor="@id/mylistview_2"
        app:layout_anchorGravity="bottom|right|end"/>


    </android.support.design.widget.CoordinatorLayout> 

Bạn để ý khi cuộn, ActionBar và Hình ảnh xảy ra các hiệu ứng như cấu hình. Với ảnh có thuộc tính app:layout_collapseMode="parallax", tạo ra hiệu ứng thay đổi điểm nhìn khi vuốt. Với ActionBar thì có app:layout_collapseMode="pin" nó sẽ không mất đi sau khi vuốt ngược lên.

BottomSheet trong CoordinatorLayout

Một View có thể vuốt từ dưới lên để xuất hiện và vuốt xuống để ẩn đi gọi là BottomSheet

Có 2 kiểu BottomSheet, một loại cố định trong Layout, có thể bị ẩn hay hiện thị, nghĩa là luôn tồn tại (Persistent Modal Sheet), một loại gọi là được chèn vào bằng Dialog Fragments, sử dụng BottomSheetDialogFragment

Tạo Persistent Modal Sheet

Khá đơn giản, thường cho thêm vào Layout một NestedScrollView để chứa nội dung của BottomSheet, rồi gán cho nó Behavior xây dựng săn của thư viện có tên là: BottomSheetBehavior (android.support.design.widget.BottomSheetBehavior). Lúc nó dưới sự tác động của CoordinatorLayout và ứng xử của BottomSheetBehavior thì NestedScrollView sẽ hoạt động là một BottomView.
Trở lại ví dụ trên, ta sẽ thêm một NestedScrollView, res\layout\activity_coordinator.xml nội dung như sau:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    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:fitsSystemWindows="false"
    tools:context="net.xuanthulba.coordinator.CoordinatorActivity">


    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/AppTheme.AppBarOverlay"

        android:layout_height="wrap_content">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:toolbarId="@+id/toolbar"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:id="@+id/image"
                android:layout_width="match_parent"
                android:layout_height="180dp"
                android:scaleType="centerCrop"
                android:src="@drawable/forest"
                app:layout_collapseMode="parallax"/>


            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:layout_scrollFlags="scroll|enterAlways"
                app:titleTextColor="@android:color/white" />


        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>


    <LinearLayout
        android:id="@+id/mainlayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#1db182"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        app:layout_anchorGravity="bottom">

        <android.support.v7.widget.RecyclerView
            android:layout_margin="10dp"
            android:padding="5dp"
            android:id="@+id/mylistview"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@android:color/holo_orange_light" />

        <android.support.v7.widget.RecyclerView
            android:layout_margin="10dp"
            android:padding="5dp"
            android:id="@+id/mylistview_2"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@android:color/holo_red_dark" />
    </LinearLayout>

    <TextView
        android:id="@+id/txtTest"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="25dp"
        android:background="@android:color/holo_green_dark"
        android:padding="10dp"
        android:text="This is a TextView in CoordinatorLayout"
        app:layout_anchor="@+id/mainlayout"
        app:layout_anchorGravity="right|top"
        app:layout_behavior="net.xuanthulba.coordinator.FirstBehavior" />


    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:layout_margin="16dp"
        android:src="@drawable/iconfab"
        android:tint="#d6bed1"
        app:layout_anchor="@id/mylistview_2"
        app:layout_anchorGravity="bottom|right|end"/>
<--Phần mới thêm -->
    <android.support.v4.widget.NestedScrollView
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:clipToPadding="true"
        app:behavior_hideable="false"
        app:behavior_peekHeight="30dp"
        android:background="@android:color/holo_purple"
        app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
        <LinearLayout
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:layout_height="wrap_content">
            <TextView
                android:text="BottomSheet"
                android:layout_width="match_parent"
                android:background="#e66f00"
                android:layout_height="30dp" />
            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="
Bottom sheets can be dismissed by swiping
the bottom sheet down, by touching an
explicit control such as an X in the
app bar, or by touching the system back
button (Android). Modal bottom sheets
can also be dismissed by touching outside
of the bottom sheet.
Dismiss by swiping
the bottom sheet down"
                android:padding="16dp"
                android:textSize="16sp"/>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>

</android.support.design.widget.CoordinatorLayout> 
Giải thích các tham số thiết lập cho NestedScrollView:
  • app:behavior_hideable="false" nếu là true thì cho phép kéo xuống sẽ ẩn hoàn toàn View, ở đây chọn false thì khi kéo xuống nó sẽ giữ lại phần nhô lên (peek).
  • app:behavior_peekHeight="30dp" thiết lập chiều cao phần nhô lên
  • app:layout_behavior="android.support.design.widget.BottomSheetBehavior" thiết lập Behavior cho NestedScrollView thành BottomSheetBehavior
Chạy thử Code trên

setBottomSheetCallback
Giờ nếu muốn bắt các sự kiện như: khi BottomSheet mở rộng lên, thu hẹp xuống, khi đang cuộn ... thì trong code bạn sẽ lấy đối tượng BottomSheetBehavior gán cho NestedScrollView, rồi thiết lập lập một CallBack có tên là BottomSheetCallback cho nó bằng phương thức setBottomSheetCallback
Lấy BottomSheetCallback đã gán cho NestedScrollView ở trên
final BottomSheetBehavior bottomSheetBehavior
    = BottomSheetBehavior.from(findViewById(R.id.bottom_sheet));
Tạo đối tượng BottomSheetCallback và gán nó cho bottomSheetBehavior, ví dụ trong onCreate
final View mainlayout = findViewById(R.id.mainlayout);
final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.bottom_sheet));
BottomSheetBehavior.BottomSheetCallback bottomSheetCallback =  new BottomSheetBehavior.BottomSheetCallback() {
    @Override
    public void onStateChanged(@NonNull View bottomSheet, int newState) {
        switch (newState) {
            case BottomSheetBehavior.STATE_HIDDEN:
                //View bị ẩn
                break;
            case BottomSheetBehavior.STATE_EXPANDED:
                //View mở rộng lên
                Toast.makeText(bottomSheet.getContext(), "Mở rộng", Toast.LENGTH_SHORT).show();
                break;

            case BottomSheetBehavior.STATE_DRAGGING:
                //Bắt đầu kéo View
                break;
            case BottomSheetBehavior.STATE_COLLAPSED:
                //View thu gọn lại
                Toast.makeText(bottomSheet.getContext(), "Thu gọn", Toast.LENGTH_SHORT).show();
                break;

            default:

                break;
        }

    }

    @Override
    public void onSlide(@NonNull View bottomSheet, float slideOffset) {
        //Vị trí tương đối của View khi trượt, slideOffset = -1 đến 1
        //Khi nhận từ 0 -> 1 thì sheet đang chuyển từ thu hẹp sang
        //mở rộng.

        //Code sau sẽ làm mờ mainlayout khi kéo sheet lên và
        //ngược lại
        float alpha = 1 - slideOffset;
        if (alpha >= 0.5)
            mainlayout.animate().alpha(alpha).start();
    }
};

bottomSheetBehavior.setBottomSheetCallback(bottomSheetCallback);
Chạy thử code trên

Sử dụng BottomSheetDialogFragment tạo BottomSheet

Bạn tạo ra một Fragment kế thừa từ BottomSheetDialogFragment (sau đó sử dụng kỹ thuật đưa Fragment vào để hiện thị BottomSheet).
Đầu tiên tạo layout/fragment_bottom_sheet.xml để chứa nội dung
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:text="BottomSheet - Dialog"
        android:layout_width="match_parent"
        android:gravity="center"
        android:background="#00ba28"
        android:layout_height="30dp" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="
Bottom sheets can be dismissed by swiping
the bottom sheet down, by touching an
explicit control such as an X in the
app bar, or by touching the system back
button (Android). Modal bottom sheets
can also be dismissed by touching outside
of the bottom sheet.
Dismiss by swiping
the bottom sheet down"
        android:padding="16dp"
        android:textSize="16sp"/>
</LinearLayout> 
Tạo Fragment kế thừa từ BottomSheetDialogFragment, ví dụ đặt tên là FirstBottomSheetDialogFragment
public class FirstBottomSheetDialogFragment extends BottomSheetDialogFragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater,
                            @Nullable ViewGroup container,
                            @Nullable Bundle savedInstanceState) {
        //Tạo View của Fragment
        return inflater.inflate(R.layout.fragment_bottom_sheet, container);
    }
}
Code sử dụng FirstBottomSheetDialogFragment, trường hợp này ví dụ bấm vào txtTest thì chèn Fragment trên
Trong onCreate:
findViewById(R.id.txtTest).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        FirstBottomSheetDialogFragment myDialog
            = new FirstBottomSheetDialogFragment();

        FragmentManager fm = getSupportFragmentManager();
        myDialog.show(fm, "FirstBottomSheetDialogFragment");
    }
});
Chạy thử code trên


Nguồn: https://xuanthulab.net/coordinatorlayout-2-ung-dung-mot-so-loai-view.html
Thứ Tư, 7 tháng 3, 2018

đơn vị px, dp, pt, in, mm, dip trong Androi

Kích thước màn hình

Màn hình hiện đại dù hình ảnh rất mịn mạng, nhưng thực chất được tạo ra từ hàng ngàn những điểm rất nhỏ, mỗi điểm nhỏ đó được gọi gọi là điểm ảnh pixel ký hiệu là px, chúng bố trí tạo ra một lưới các điểm ảnh.


https://xuanthulab.net/tim-hieu-cac-don-vi-px-dp-pt-in-mm-dip-trong-androi.html

Các pixel được trí từ trái qua phải là X, từ trên xuống dưới là trục Y (khoảng cách 2 pixel theo trục X và Y có thể bằng nhau, có thể khác nhau). Ví dụ một màn hình 320 x 480 (width x height) thì có 320 pixel theo trục X và 480 pixel theo trục Y.

Khái niệm về kích thước màn hình
Kích thước màn hình mà mọi người vẫn quen sử dụng đối với smartphone, laptop, Tivi đó là đơn vị inch (1 inch = 2.54 cm = 25.4 mm) (màn hình laptop 17 inch chẳng hạn). Đây là kích thước đo bằng đường chéotừ góc trên bên trái tới góc dưới bên phải màn hình. Nếu biết chiều rộng, chiều cao màn hình thì có thể tính toán ra kích thước màn hình theo khái niệm trên. Như ví dụ sau, màn hình kích thước 3.2 inch

Mật độ điểm ảnh PPI và DPI

PPI Pixels Per Inch - Mật độ điểm ảnh có nghĩa là số lượng pixel trên một Inch. Như vậy màn hình nào có mật độ điểm ảnh lớn sẽ cho ảnh sắc nét hơn, vì thế đây là thông số quan tâm khi mua một thiết bị mới.
DPI Dots Per Inch - Mật điểm điểm trên một inch, khá giống với PPI, khái niệm này rất đầu tiên dùng trong kỹ thuật in, DPI là số điểm ảnh mà máy in ra trên một inch. Một cách tương đối thì PPI là mật độ điểm ảnh trên màn hình, DPI là mật độ điểm ảnh trên máy in.

Để kích thước ảnh hiện thị một cách độc lập với PPI (kích thước ảnh giống nhau trên các màn hình có PPI khác nhau), khi vẽ ảnh, xây dựng tài nguyên Android có thể dùng tới đơn vị phái sinh từ DPI là . Đơn vị dp (Density Independent Pixel) được tính dựa theo màn hình gốc ban đầu làm chuẩn. Android phần chia ra các loại màn hình theo DPI
  • MDPI - màn hình có độ phân giải trung bình - là màn hình làm chuẩn: 1dp = 1px (có khoảng 160dp trong 1 in, mật độ density = 1)
  • LPDI - màn hình có độ phân giải thấp: 1dp = 0,75px, (density = 0,75)
  • HDPI - màn hình có độ phân giải cao: 1dp = 1,5px, (density = 1,5)
  • XHDPI - màn hình có độ phân giải siêu cao: 1dp = 2px, (density = 2)
  • XXDPI - màn hình có độ phân giải siêu siêu cao: 1dp = 3pixel, (density = 3)
  • XXDPI - màn hình có độ phân giải siêu siêu siêu cao: 1dp = 4pixel, (density = 4)

Các đơn vị xác định kích thước, khoảng cách màn hình trong lập trình Android / IOS

Có hai nhóm đơn vị, thứ nhất là các đơn vị độc lập mật độ và các đơn vị phụ thuộc vào mật độ điểm ảnh trên màn hình.
  • Đơn vị phụ thuộc PPI, có nghĩa nó loại đơn vị tương đối, kích thước theo đơn vị này khi thể hiện thật trên các màn hình có mật độ điểm ảnh khác nhau là khác nhau, đó là đơn vị px
    • px - một pixel thực tế trên màn hình (Khi bạn vẽ ảnh 100px thì kích thước thật (theo mm, cm ...) thể hiện trên màn hình là khác nhau cho các màn hình có mật độ điểm ảnh khác nhau)
  • Đơn vị độc lập với PPI có nghĩa là nó thể hiện đúng kích thước vật lý, dù đó là màn hình nào. Đó là các đơn in, mm, pt, dp, sp
    • in - (1 in = 2.54cm)- kích thước vật lý thật trên mọi màn hình
    • mm - kích thước vật lý 1mm, giống nhau cho mọi màn hình
    • pt - điểm (point), đây là đơn vị phổ biến dùng biểu diễn kích thước font chữ (72pt = 1inch = 25.4mm) hay 1pt = (1/72)in
    • dp - (hoặc ký hiệu dip) mặc dù xếp vào loại đơn vị độc lập với PPI, nhưng một dp trên các màn hình khác nhau có một chút sai số nhỏ (chỉ xấp xỉ bằng nhau trên các loại màn hình khác nhau). 1 in ≃ 160 dp
    • sp - (scale independent pixel), được dùng chủ yếu cho cỡ chữ, nó khá tương đồng với sp nhằm mục đích chữ có cỡ giống nhau sẽ hiện thị kích thước giống nhau trên các màn hình có PPI khác nhau. Tỷ lệ giữa sp và dp có thể điều chỉnh lại bởi người dùng.
Tỷ lệ tương đối giữa sp và dp có thể điều chỉnh lại

Các đơn vị trên: px, in, mm, pt, dp, sp đều có thể dùng trong XML trình bày layout, dùng trong các values.xml ...
Vì dụ trong XML trình bày layout
<TextView
    android:layout_width="100dp"
    android:textSize="16sp"
    android:layout_marginTop="1in"
    android:layout_marginLeft="8mm"
    android:layout_height="wrap_content"
    android:text="Example" />
Ví dụ trong: values/dimens.xml
<resources>
    <dimen name="text_margin">16dp</dimen>
    <dimen name="myftextsize">14sp</dimen>
    <dimen name="myavatar">100px</dimen>
    <dimen name="myfontsize">20pt</dimen>
</resources>
Việc sự dụng đơn vị như thế nào do ý thích của bạn, tuy nhiên để đảm bảo độc lập về thiết bị thì nên dùng dp trong trình bày đơn vị liên quan đến layout, như kích thước, margin, padding, width, height ... Dùng sp cho cơ chữ.

Sử dụng các đơn vị trong Android - Thông tin màn hình thiết bị

Sử dụng DisplayMetrics để biết thông tin hiện thị trên màn hình thiết bị

DisplayMetrics (android.util.DisplayMetrics) là lớp chứa các hằng số, phương thức cho bạn biết thông tin về màn hình thiết bị, cấu hình hiện thị. Để có được một DisplayMetrics có thể làm như sau:
Trong các Activity
 DisplayMetrics metrics = new DisplayMetrics();
 getWindowManager().getDefaultDisplay().getMetrics(metrics);
Khi đã có Context
 DisplayMetrics metrics = context.getResources().getDisplayMetrics()
DisplayMetrics cung cấp các thuộc tính
density Mật độ logic (tương quan DPI và PPI)
densityDpi chính là DPI
heightPixels, widthPixels Lấy chiều cao, chiều rộng hiện thị theo px
scaledDensity Tỷ lệ cho font chữ (đơn vị sp), scaledDensity thường là bằng với density, trừ khi người dùng điều chỉnh
xdpi, ydpi Số pixel trong 1 inch chính xác theo chiều X (ngang), chiều Y

Chuyển đổi giữa các đơn vị

Nhiều trường hợp bạn có nhu cầu chuyển đổi giá trị theo đơn vị này sang giá trị theo đơn vị khác trong lập trình Android
Chuyển từ DP sang PX
public static int convertDpToPixels(float dp, Context context) {
    int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
    context.getResources().getDisplayMetrics());
    return px;
}
Chuyển từ SP sang PX
public static int convertSpToPixels(float sp, Context context) {
    int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
    context.getResources().getDisplayMetrics());
    return px;
}
Từ px tính ra dp
dp = (kích_thước_theo_pixel * 160) / density
Ngoài ra bạn cũng có thể khai báo giá trị 1 đơn vị trong values/dimens.xml rồi đọc giá trị đó, để biết 1 đơn vị mô tả có bao nhiêu pixel. Ví dụ trong values/dimens.xml thêm các dòng:
<resources>
<dimen name="one_unit_dp">1dp</dimen>
<dimen name="one_unit_sp">1sp</dimen>
<dimen name="one_unit_pt">1pt</dimen>
<dimen name="one_unit_in">1in</dimen>
<dimen name="one_unit_mm">1mm</dimen>
</resources>
int sizeInPixel = context.getResources().getDimensionPixelSize(R.dimen.one_unit_dp);

Mật độ màn hình và tài nguyên trong dự án Adnroid

Mật độ màn hình density chính là tỷ tệ tượng quan giữa số point và pixel, để lấy được giá trị này trong Android có đoạn code sau:
    float density = context.getResources().getDisplayMetrics().density;
    if (density == 0.75)
    {
        //Màn hình LDPI
    }
    else if (density == 1)
    {
        //Màn hình MDPI
    }
    else if (density == 1.5)
    {
        //Màn hình HDPI
    }
    else if (density == 2)
    {
        //Màn hình XHDPI
    }
    else if (density == 3)
    {
        //Màn hình XXHDPI
    }
    else if (density == 4)
    {
        //Màn hình XXXHDPI
    }
Chính từ giá trị mật độ này, khi xây dựng các tài nguyên như hình ảnh, các ảnh có lưu trong thư mục trùng với tên màn hình tương ứng, khi ứng dụng chạy nó sẽ tìm tới ldpi mdpi hdpi xhdpi xxhdpi xxxhdpi nodpi tvdpi tài nguyên phù hợp với màn hình

Chú ý, ảnh lưu trong folder-nodpi sẽ vẽ đúng kích thước pixel ảnh, tương ứng số pixel màn hình

https://xuanthulab.net/tim-hieu-cac-don-vi-px-dp-pt-in-mm-dip-trong-androi.html
Thứ Ba, 6 tháng 3, 2018

CoordinatorLayout (1) xây dựng bố cục, Behavior và Nested Scroll


Giới thiệu về CoordinatorLayout trong Android

CoordinatorLayout là một lớp mở rộng từ ViewGroup nó khá giống với FrameLayout được sử dụng trong thiết kế UI. Thường sử dụng CoordinatorLayout để thiết kế layout chính của ứng dụng, nó như là phần tử chứa các View con, cung cấp khả năng tương tác mềm dẻo giữa các View con trong nó.
Sử dụng CoordinatorLayout bạn có thể thiết lập nhiều sự tương tác khác nhau giữa phần tử View cha và các View con, hay giữa các View con với nhau.
https://xuanthulab.net/coordinatorlayout-1-xay-dung-bo-cuc-behavior-va-nested-scroll.html
Để sử dụng CoordinatorLayout đảm bảo tích hợp thự viện vào build.gradle với dòng mã:
compile 'com.android.support:design:26.1.0' //hoặc phiên bản nào bạn thích
Chèn CoordinatorLayout vào XML cú pháp dạng sau:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    //...
     />
Khi các View con nằm trong CoordinatorLayout thì nó có thể thiết lập các thuộc tính
Thuộc tính Giá trị Mô tả
app:layout_anchor ID của một phần tử neo vào Chỉ ra phần tử mà phần tử sẽ neo vào để xác định vị trí, ví dụ: app:layout_anchor="@android:id/toolbar"
app:layout_anchorGravity Kết hợp các giá trị hằng số của lớp Gravity: top, bottom, left, right, start, end ... Chỉ ra cách thức mà phần tử neo theo phần tử khác (dịch trên trên phần tử khác). Ví dụ: app:layout_anchorGravity="bottom|left", neo vào biên phía dưới, bên trái của phần tử chỉ ra bởi app:layout_anchor
app:layout_behavior Một String String này tham chiếu lớp lớp mở rộng từ lớp CoordinatorLayout.Behavior, nó sẽ định nghĩa ứng xử của View bên trong CoordinatorLayout, Ví dụ:app:layout_behavior="FloatingActionButton.Behavior", app:layout_behavior="com.mydomain.MyBehavior"
Ví dụ trình bày layout sử dụng CoordinatorLayout trong file: res\layout\activity_coordinator.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:fitsSystemWindows="true"
    tools:context="net.xuanthulba.coordinator.CoordinatorActivity">


    <TextView
        android:id="@+id/frID"
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:text="BLOCK1"
        android:gravity="center"

        android:background="#e65100" />


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:layout_marginTop="160dp"
        android:background="#1db182"
        app:layout_anchorGravity="bottom">

    </LinearLayout>

    <TextView
        android:id="@+id/txtTest"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="25dp"
        android:background="@android:color/holo_green_dark"
        android:padding="10dp"
        android:text="This is a TextView in CoordinatorLayout"
        app:layout_anchor="@+id/frID"
        app:layout_anchorGravity="right|bottom"
        app:layout_behavior="net.xuanthulba.coordinator.FirstBehavior" />


</android.support.design.widget.CoordinatorLayout>

Bạn cũng có thể thiết lập app:layout_anchorGravity bằng code (kể các thuộc tính khác)
public class CoordinatorActivity extends AppCompatActivity {

    TextView txtTest;
    CoordinatorLayout.LayoutParams txtTestLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_coordinator);

        txtTest = findViewById(R.id.txtTest);
        txtTestLayout = (CoordinatorLayout.LayoutParams)txtTest.getLayoutParams();
        txtTestLayout.anchorGravity = Gravity.TOP | Gravity.CENTER;

    }
}

Tạo Behavior để điều khiển ứng xử của các phần tử trong CoordinatorLayout

Như trên đã nói, các phần tử View bên trong CoordinatorLayout có thể thiết lập thuộc tính app:layout_behavior trỏ đến một lớp Behavior, để điều khiển cách ứng xử của View con. Nói cách khác nếu thiết lập Behavior cho View con, thì nó sẽ nhận được các thông tin như các sự kiện chạm, vuốt, chèn cửa sổ, cuộn ... được gửi tới bởi View con khác.
Cách tạo một Behavior là tạo ra một lớp theo dạng như sau (đây mục đích tạo ra Behavior dùng cho TextView)
package net.xuanthulba.coordinator;
//...
public class FirstBehavior extends CoordinatorLayout.Behavior {
  public FirstBehavior() {

    }

    public FirstBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}
Như vậy đã có Behavior là net.xuanthulba.coordinator.FirstBehavior, ví dụ này chỉ khai báo nó chưa làm gì, để thực hiện được chức năng nào đó bạn sẽ cần quá tải (overrided) một số phương thức khác nhau tùy mục đích (xem mục dưới)
Giờ ta có thể gán Behavior tự xây dựng cho phần tử TextView, thực hiện bằng một số cách như:

Gắn Behavior vào View bằng code

TextView txtTest = findViewById(R.id.txtTest);
CoordinatorLayout.LayoutParams = txtTestLayout =
    (CoordinatorLayout.LayoutParams)txtTest.getLayoutParams();
FirstBehavior behavior = new FirstBehavior();
txtTestLayout.setBehavior(behavior);

Gắn Behavior vào View bằng XML (layout)

<TextView
    android:id="@+id/txtTest"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_margin="10dp"
    android:background="@android:color/holo_orange_light"
    android:padding="10dp"
    android:text="This is a TextView in CoordinatorLayout"
    app:layout_anchor="@+id/frID"
    app:layout_anchorGravity="right|bottom"
    app:layout_behavior="net.xuanthulba.coordinator.FirstBehavior" />

Tự động gắn Behavior vào View

Cách này bạn phải khai báo lớp View, sau đó tử dụng trong Layout thì nó tự động gắn Behavior. Để làm điều này sử dụng Annotation trong Android, ví dụ
@CoordinatorLayout.DefaultBehavior(FirstBehavior.class)
class MyTextView extends TextView {
  //..
}
Giờ trong Layout XML, nếu sử dụng MyTextView nó sẽ tự động có Behavior trên
<MyTextView
    android:id="@+id/txtTest"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_margin="10dp"
    android:background="@android:color/holo_orange_light"
    android:padding="10dp"
    android:text="This is a TextView in CoordinatorLayout"
    app:layout_anchor="@+id/frID"
    app:layout_anchorGravity="right|bottom"
Tiếp sau đây, triển khai chi tiết Behavior theo từng mục đích

Bắt sự kiện Touch trong Behavior

Với CoordinatorLayout phương thức onInterceptTouchEvent() của nó sẽ bỏ qua, thay vào đó nó sẽ gọi onInterceptTouchEvent() của Behavior, và điều này giúp cho Behavior (cách khác chính là các View con) chặn được sự kiện Touch. Bằng cách trả về true thì Behavior sau đó sẽ nhận các sự kiện thông qua onTouchEvent()
Ngoài ra còn có phương thức, blocksInteractionBelow để cho CoordinatorLayout biết các View phía sau có bị khóa tương tác Touch không
Trở lại lớp FirstBehavior ta sẽ quá tải các phương thức: onInterceptTouchEvent, onTouchEvent
public class FirstBehavior extends CoordinatorLayout.Behavior<TextView> {
    public FirstBehavior() {

    }

    public FirstBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent,
                                        TextView child, MotionEvent ev) {
        //Hàm này nhận sự kiện Touch (down) ban đầu nếu nhấn trong CoordinatorLayout
        //Nếu thiết lập trả về true thì onToucheEvent sẽ nhận các sự kiện
        //Tiếp theo và các View con khác không nhận được Touch

        child.setText(ev.getAction()+"|"+(int)ev.getX()+"|"+(int)ev.getY());
        return true;

    }

    @Override
    public boolean onTouchEvent(CoordinatorLayout parent,
                                TextView child, MotionEvent ev) {
        child.setText(ev.getAction()+"|"+(int)ev.getX()+"|"+(int)ev.getY());
        return true;
    }


}
Chạy thử, bạn có thể chạm vuốt bên bất ký đâu trên màn hình

Bắt sự kiện Measurement, Layout

Measurement, Layout là thành phần cần thiết để Android vẽ các View. Khi CoordinatorLayout xác định kích thước các View con, bạn có thể chặn lại để can thiệp bằng cách quá tải hàm onMeasureChild(), onLayoutChild()
Đoạn mã sau, thêm maxWidth nếu kích thước lớn hơn chiều rộng
public class FirstBehavior extends CoordinatorLayout.Behavior<TextView> {
    //...
    @Override
    public boolean onMeasureChild(CoordinatorLayout parent, V child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        if (mMaxWidth <= 0) {
            // No max width means this Behavior is a no-op
            return false;
        }
        int widthMode = MeasureSpec.getMode(parentWidthMeasureSpec);
        int width = MeasureSpec.getSize(parentWidthMeasureSpec);

        if (widthMode == MeasureSpec.UNSPECIFIED || width > mMaxWidth) {
            // Sorry to impose here, but max width is kind of a big deal
            width = mMaxWidth;
            widthMode = MeasureSpec.AT_MOST;
            parent.onMeasureChild(child,
                    MeasureSpec.makeMeasureSpec(width, widthMode), widthUsed,
                    parentHeightMeasureSpec, heightUsed);
            // We've measured the View, so CoordinatorLayout doesn't have to
            return true;
        }

        // Looks like the default measurement will work great
        return false;
    }
}

Sự phụ thuộc nhau của View trong CoordinatorLayout

Behavior của View này bạn có thể nhận thông tin khi một View khác nó phụ thuộc vào thay đổi vị trí, kích thước ... Khi View nó phụ thuộc thay đổi vị trí CoordinatorLayout sẽ gọi phương thức onDependentViewChanged() của Behavior. Phương thức này xây dựng như sau
public class FirstBehavior extends CoordinatorLayout.Behavior<TextView> {
    //Mỗi khi View nó dựa vào thay đổi vị trí, dịch chuyển, kích cỡ ... sẽ gọi hàm này
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent,
                                          TextView child, View dependency) {
        //...Code của bạn
        return super.onDependentViewChanged(parent, child, dependency);
    }
    //..
}
Tuy nhiên để onDependentViewChanged được CoordinatorLayout gọi bạn cần thiết lập View nào nó phụ thuộc vào, trong Layout có thể làm điều này chính là thuộc tính:
app:layout_anchor="@+id/frID"
Hoặc bạn quá tải phương thức layoutDependsOn trả về true View nào cần phù thuộc
public class FirstBehavior extends CoordinatorLayout.Behavior<TextView> {
//..
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent,
                                   TextView child, View dependency) {
     //Kiểm tra nếu dựa vào dependency thì trả về true, nếu không dựa vào dependency
     //trả về false
     //Trả về true thì onDependentViewChanged sẽ được gọi mỗi khi dependency thay đổi
    }
//...
}
Ví dụ áp dụng, bạn có thể neo FloatingActionBar vào AppBarLayout, FloatingActionButton.Behavior sẽ nhận được thông tin mỗi khi AppBarLayout cuộn khỏi mà hình thì ẩn đi FAB. Xem mã nguồn FloatingActionButton.Behavior

Nested Scrolling

Một số loại View như RecyclerView, NestedScrollView ... bên trong CoordinatorLayout khi nó cuộn nội dung trong nó thì nó phát sinh sự kiện cuộn đó tạm gọi là Nested Scrolling, bất kỳ View con nào trong CoordinatorLayout đều có cơ hội nhận được sự kiện này thông qua Behavior
Các phương thức có thể overried để xử lý nhận sự kiện Nested Scrolling
  • onStartNestedScroll gọi khi View con khởi tạo quá trình Nested Scroll trong CoordinatorLayout
  • onNestedFling gọi khi View con fling (cử chỉ vuốt nhanh)
  • onNestedPreFling gọi khi View con bắt đầu fling
  • onNestedPreScroll gọi khi tiến trình Nested scroll chuẩn bị cập nhật mới
  • onNestedScroll gọi khi nested scroll
  • onNestedScrollAccepted gọi khi nested scroll đã chấp nhận trong CoordinatorLayout
  • onStopNestedScroll gọi khi quá trình Nested scroll kết thúc
Để thực hiện ví dụ trường hợp trên, cập nhật lại res\layout\activity_coordinator.xml sử dụng thêm RecyclerView
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:fitsSystemWindows="true"
    tools:context="net.xuanthulba.coordinator.CoordinatorActivity">


    <TextView
        android:id="@+id/frID"
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:text="BLOCK1"
        android:gravity="center"

        android:background="#e65100" />


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:layout_marginTop="160dp"
        android:background="#1db182"
        app:layout_anchorGravity="bottom">

        <android.support.v7.widget.RecyclerView
            android:layout_margin="10dp"
            android:padding="5dp"
            android:id="@+id/mylistview"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@android:color/holo_orange_light" />

        <android.support.v7.widget.RecyclerView
            android:layout_margin="10dp"
            android:padding="5dp"
            android:id="@+id/mylistview_2"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@android:color/holo_red_dark" />
    </LinearLayout>

    <TextView
        android:id="@+id/txtTest"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="25dp"
        android:background="@android:color/holo_green_dark"
        android:padding="10dp"
        android:text="This is a TextView in CoordinatorLayout"
        app:layout_anchor="@+id/frID"
        app:layout_anchorGravity="right|bottom"
        app:layout_behavior="net.xuanthulba.coordinator.FirstBehavior" />


</android.support.design.widget.CoordinatorLayout>
Tiến hành thêm vào FirstBehavior các phương thức overrided ở trên:
public class FirstBehavior extends CoordinatorLayout.Behavior<TextView> {
//..
    //CoordinatorLayout gọi bất kỳ View nào có khả năng Nested Scrolll bắt đầu đăng ký quá trình
    //Nested Scroll trong CoordinatorLayout, ví dụ này chỉ nhận Nested Scrolll từ
    //RecylerView có ID :  R.id.mylistview_2
    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                       @NonNull TextView child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        Log.i(TAG, "onStartNestedScroll:"+target.getId());
        if (target.getId() == R.id.mylistview_2)
            return true;
        return false;
    }


    //Gọi khi View con scroll
    @Override
    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull TextView child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        Log.i(TAG, "onNestedScroll:"+target.getId());
        ((TextView)coordinatorLayout.findViewById(R.id.txtTest)).setText("Scroll:"+dyConsumed);
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
    }


    @Override
    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull TextView child, @NonNull View target, int type) {
        Log.i(TAG, "onStopNestedScroll:"+target.getId());
        ((TextView)coordinatorLayout.findViewById(R.id.txtTest)).setText("StopScroll");
        super.onStopNestedScroll(coordinatorLayout, child, target, type);
    }

    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull TextView child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        Log.i(TAG, "onNestedPreScroll");
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
    }

    //onNestedPreFling: Được View con gọi khi chuẩn bị fling, nó gọi hàm này để kiểm tra điều kiện thực hiện fling
    //nếu Behavior trả về true, nghĩa là nó dùng fling và dẫn tới View con không fling nữa
    @Override
    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull TextView child, @NonNull View target, float velocityX, float velocityY) {
        Log.i("XXX", "onNestedPreFling");
        return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
    }
    //onNestedFling gọi khi View con thực hiện fling
    @Override
    public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull TextView child, @NonNull View target, float velocityX, float velocityY, boolean consumed) {
        Log.i("XXX", "onNestedFling");
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }
//..
Để đưa một danh sách ví dụ trong RecylerView bạn sử dụng code như sau (Đọc thêm riêng về RecylerView để giải thích chi tiết)
/**
 * Lớp biểu diễn Holder trong RecycleView
 */
class ElementViewHolder extends RecyclerView.ViewHolder
{
    TextView textView;

    public ElementViewHolder(TextView itemView) {
        super(itemView);
        textView = itemView;
    }

    public TextView getTextView() {
        return textView;
    }
}

/**
 * Adapter cho danh sách phần tử Text dùng trong RecycleView
 */
class ElementsAdapter extends RecyclerView.Adapter {

    LayoutInflater inflater = LayoutInflater.from(getBaseContext());
    String[] arWords = new String[] {
            "Phần Tử 1", "Phần Tử 2",
            "Phần Tử 3", "Phần Tử 4",
            "Phần Tử 5", "Phần Tử 6",
            "Phần Tử 7", "Phần Tử 8", "Phần Tử 9", "Phần Tử 10",
            "Phần Tử 11", "Phần Tử 12", "Phần Tử 13", "Phần Tử 14",
            "Phần Tử 15", "Phần Tử 16", "Phần Tử 17",
            "Phần Tử 18", "Phần Tử 19", "Phần Tử 20", "Phần Tử 21",
            "Phần Tử 22", "Phần Tử 23", "Phần Tử 24", "Phần Tử 25"};
    @Override
    public RecyclerView.ViewHolder
    onCreateViewHolder(ViewGroup parent, int viewType) {
        TextView v = (TextView)inflater
                .inflate(android.R.layout.simple_list_item_1, parent, false);
        return new ElementViewHolder(v);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ((ElementViewHolder)holder).getTextView().setText(arWords[position]);
    }

    @Override
    public int getItemCount() {
        return arWords.length;
    }
}
Trong onCreate của Activity thêm vào
//Sử dụng RecyclerView
RecyclerView listView = findViewById(R.id.mylistview);
RecyclerView.Adapter adapter = new  ElementsAdapter();
listView.setLayoutManager(new LinearLayoutManager(this));

listView.setAdapter(adapter);

RecyclerView listView2 = findViewById(R.id.mylistview_2);
RecyclerView.Adapter adapter2 = new  ElementsAdapter();
listView2.setLayoutManager(new LinearLayoutManager(this));

listView2.setAdapter(adapter);
Chạy thử và xem LogCat để biết cách ứng xử của nó như thế nào

Các View có phát sinh Nested Scroll tham khảo: NestedScrollView, RecylerView, SwipeRefreshLayout, AppBarLayout

Danh mục Gửi liên hệ

Loading...