0

Android — Viết Compound View

Trong bài viết này, tôi sẽ nói về compound view trong Android.

  • What is a compound view?
  • What are the advantages of using it?
  • How to write one?

Tôi sẽ trả lời câu hỏi này bằng cách viết một compound view đơn giản hiển thị xếp hạng từ các mạng xã hội.

What is a compound view ?

Compound view về cơ bản là một ViewGround có các hành động được xác định trước và một tập hợp các View bên trong nó. Trong ví dụ này, compound view là một LinearLayout cùng với 3 TextView bên trong, animation và hiệu ứng fadeIn.

What are the advantages of a compound view?

  • Đóng gói và tập trung xử lý logic.
  • Tránh code duplicate.
  • Dễ dàng maintain và modify trong tương lai.

Writing a compound view

Hãy tưởng tượng ứng dụng của mình hiển thị các số điểm ưa thích của ứng dụng và những POI đó có các rating được liên kết từ nhiều nguồn khác nhau, bạn sẽ hiển thị rating ở nhiều nơi, POI có thể có xếp hạng Facebook nhưng không phải là Foursquare và trong tương lai chúng ta có thể có nguồn xếp hạng mới.

Sử dụng compound view, Tôi có thể gói gọn tất cả logic này và có thể dễ dàng thay đổi nó trong tương lai nếu cần thiết.

XML Layout

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/facebookTv"
        android:textColor="@android:color/black"
        android:textSize="16sp"
        android:drawableStart="@drawable/ic_facebook"
        android:drawableLeft="@drawable/ic_facebook"
        android:drawablePadding="4dp"
        android:gravity="center_vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/yelpTv"
        android:textColor="@android:color/black"
        android:textSize="16sp"
        android:drawableStart="@drawable/ic_yelp"
        android:drawableLeft="@drawable/ic_yelp"
        android:drawablePadding="4dp"
        android:gravity="center_vertical"
        android:layout_centerVertical="true"
        android:layout_marginStart="12dp"
        android:layout_marginLeft="12dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/foursquareTv"
        android:textColor="@android:color/black"
        android:textSize="16sp"
        android:drawableStart="@drawable/ic_foursquare"
        android:drawableLeft="@drawable/ic_foursquare"
        android:layout_centerVertical="true"
        android:gravity="center_vertical"
        android:drawablePadding="4dp"
        android:layout_marginStart="12dp"
        android:layout_marginLeft="12dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</merge>

Đây là file XML layout tôi đang sử dụng để inflate vào LinearLayout, chú ý merge Đây là một thành phần quan trọng, thẻ merge là một cách để tránh ViewGroup dư thừa, nói cách khác, nếu chúng ta inflate này vào LinearLayout, chúng ta sẽ không nên sử dụng LinearLayout parent trong XML, nếu không cuối cùng chúng ta sẽ có 2 LinearLayout, 1 trong số đó không cần thiết.

Không dùng merge

  • LinearLayout -> LinearLayout -> 3 TextViews

Sử dụng merge

  • LinearLayout -> 3 TextViews

RatingsView

public class RatingsView extends LinearLayout {

    //Text views
    private TextView mFacebookTv,mYelpTv,mFoursquareTv;
    private POI mPOI;

    public RatingsView(Context context) {
        this(context,null);
    }

    public RatingsView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public RatingsView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public RatingsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }


    /**
     * Initialize view
     */
    private void init(){

        //Inflate xml resource, pass "this" as the parent, we use <merge> tag in xml to avoid
        //redundant parent, otherwise a LinearLayout will be added to this LinearLayout ending up
        //with two view groups
        inflate(getContext(),R.layout.ratings_layout,this);

        //Get references to text views
        mFacebookTv  = (TextView)findViewById(R.id.facebookTv);
        mYelpTv      = (TextView)findViewById(R.id.yelpTv);
        mFoursquareTv = (TextView)findViewById(R.id.foursquareTv);

        //Initially all views are gone
        mFacebookTv.setVisibility(GONE);
        mYelpTv.setVisibility(GONE);
        mFoursquareTv.setVisibility(GONE);

        //Reset alpha
        mFacebookTv.setAlpha(0);
        mYelpTv.setAlpha(0);
        mFoursquareTv.setAlpha(0);

        //Animate views with a nice fadeIn effect before drawing
        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {

                getViewTreeObserver().removeOnPreDrawListener(this);

                if(mPOI.hasFacebookRating())
                    ViewCompat.animate(mFacebookTv).alpha(1).setDuration(500);

                if(mPOI.hasYelpRating())
                    ViewCompat.animate(mYelpTv).alpha(1).setStartDelay(120).setDuration(500);

                if(mPOI.hasFoursquareRating())
                    ViewCompat.animate(mFoursquareTv).alpha(1).setStartDelay(240).setDuration(500);

                return false;
            }
        });
    }

    public void setPOI(POI poi) {
        mPOI = poi;
        setupView();
    }

    //This method is called to show the ratings
    private void setupView(){

        if(mPOI.hasFacebookRating()){
            mFacebookTv.setVisibility(VISIBLE);
            mFacebookTv.setText(String.valueOf(mPOI.getFacebookRating()));
        }

        if(mPOI.hasFoursquareRating()) {
            mFoursquareTv.setVisibility(VISIBLE);
            mFoursquareTv.setText(String.valueOf(mPOI.getFoursquareRating()));
        }

        if(mPOI.hasYelpRating()){
            mYelpTv.setVisibility(VISIBLE);
            mYelpTv.setText(String.valueOf(mPOI.getYelpRating()));
        }

    }

}

Method init() là nơi logic xảy ra, chúng inflate vào XML layout và truyền "this" vào parameter thứ 3, các View inflate sẽ được thêm vào như là children của LinearLayout. Sau đó, tôi set visibility cho views là gone và thêm animation đơn giản fadeIn.

setPOI() nhận một đối tượng POI và thiết lập chế độ xem để hiện thị xếp hạng khả dụng cho POI đó. Và điều đó, tôi biết có một compound view mà tôi có thể sử dụng ở mọi nơi trong ứng dụng của mình.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        //Get rating view
        RatingsView ratingsView = (RatingsView)findViewById(R.id.ratingsView);

        //Create a dummy POI with some values for ratings
        POI poi = new POI();
        poi.setFacebookRating(8.8f);
        poi.setYelpRating(7f);
        poi.setFoursquareRating(6.3f);

        //Pass the POI to rating view
        ratingsView.setPOI(poi);

    }
}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <sserra.compoundview.RatingsView
        android:id="@+id/ratingsView"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>

Bạn có thể thấy rằng điều này tránh duplicate logic mỗi lần tôi muốn hiển thị rating, nếu tôi cần thêm rating mới, tôi chỉ cần thay đổi nó trong class RatingsView để tránh code duplicate.

Tài liệu tham khảo

https://medium.com/@Sserra90/android-writing-a-compound-view-1eacbf1957fc


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí