Android swipe view giống như ứng dụng Tinder

Mục tiêu bài hướng dẫn

  1. Chúng ta sẽ tạo swipe view như được sử dụng trong Tinder. Vuốt sang phải được coi là chấp nhận và vuốt sang trái bị từ chối.
  2. Như chúng ta có thể thấy trong ảnh gif ở trên, có rất nhiều thứ xảy ra. Tất cả những thứ này được triển khai trong lớp SwipePlaceHolderView của thư viện PlaceHolderView.
  3. Chúng ta sẽ load ảnh từ các url và và cho ảnh hiển thị lên. Để làm đượcđiều này, chúng ta sẽ sử dụng thư viện Glide.
  4. Dữ liệu sẽ được lấy ra từ tệp json, tệp json này sẽ được lưu trữ trong thư mục asset.
  5. Sử dụng thư viện Gson để parse json.

Bước 1

Tạo project mới như thông thường Cấu hình file build.gradle như sau:

android {
    ...
    sourceSets {
        main {
            assets.srcDirs = ['src/main/assets', 'src/main/assets/']
            res.srcDirs = ['src/main/res', 'src/main/res/drawable']
        }
    }
}

dependencies {
    ...
    compile 'com.mindorks:placeholderview:0.7.1'
    compile 'com.android.support:cardview-v7:25.3.1'
    compile 'com.github.bumptech.glide:glide:3.7.0'
    compile 'com.google.code.gson:gson:2.7'
}

Lưu ý:

  1. Thêm folder asset để chứa file json.
  2. Sử dụng cardview để hiển thị ảnh.
  3. Thêm internet permission vào file AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>

Bước 2

Tạo file giao diện màn hình chính activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/grey">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:layout_gravity="bottom"
        android:gravity="center"
        android:orientation="horizontal">
        <ImageButton
            android:id="@+id/rejectBtn"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="@drawable/ic_cancel"/>
        <ImageButton
            android:id="@+id/acceptBtn"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_marginLeft="30dp"
            android:background="@drawable/ic_heart"/>
    </LinearLayout>
    <com.mindorks.placeholderview.SwipePlaceHolderView
        android:id="@+id/swipeView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</FrameLayout>

Bước 3

Tạo file tinder_card_view.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="350dp"
    android:layout_height="425dp"
    android:layout_marginBottom="50dp"
    android:orientation="vertical">
    <android.support.v7.widget.CardView
        android:orientation="vertical"
        android:background="@color/white"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        app:cardCornerRadius="7dp"
        app:cardElevation="4dp">
        <ImageView
            android:id="@+id/profileImageView"
            android:scaleType="centerCrop"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="75dp"/>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="75dp"
            android:orientation="vertical"
            android:layout_gravity="bottom"
            android:gravity="center|left"
            android:paddingLeft="20dp">
            <TextView
                android:id="@+id/nameAgeTxt"
                android:layout_width="wrap_content"
                android:textColor="@color/colorPrimaryDark"
                android:textSize="18dp"
                android:textStyle="bold"
                android:layout_height="wrap_content"/>
            <TextView
                android:id="@+id/locationNameTxt"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/colorPrimaryDark"
                android:textSize="14dp"
                android:textStyle="normal"/>
        </LinearLayout>
    </android.support.v7.widget.CardView>
</FrameLayout>

Bước 4

Tạo file tinder_swipe_in_msg_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="350dp"
    android:layout_height="425dp">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="32sp"
        android:textStyle="bold"
        android:layout_margin="40dp"
        android:textColor="@android:color/holo_green_light"
        android:text="Accept"/>
</LinearLayout>

Bước 5

Tạo file tinder_swipe_out_msg_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="350dp"
    android:gravity="right"
    android:layout_height="425dp">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="32sp"
        android:textStyle="bold"
        android:layout_margin="40dp"
        android:textColor="@android:color/holo_red_light"
        android:text="Reject"/>
</LinearLayout>

Bước 6

File json có nội dung như sau:

[  
   {  
      "url":"https://pbs.twimg.com/profile_images/572905100960485376/GK09QnNG.jpeg",
      "name":"Sofia",
      "age":20,
      "location":"New York"
   },
   {  
      "url":"http://cdn.cavemancircus.com//wp-content/uploads/images/2015/june/pretty_girls_3/pretty_girls_15.jpg",
      "name":"Roma",
      "age":22,
      "location":"California"
   },
   {  
      "url":"http://i.imgur.com/N6SaAlZ.jpg",
      "name":"Zoya",
      "age":23,
      "location":"Atlantic City"
   },
   {  
      "url":"http://cdn.cavemancircus.com//wp-content/uploads/images/2015/january/pretty_girls_2/pretty_girls_5.jpg",
      "name":"Carol",
      "age":19,
      "location":"New City"
   },
   {  
      "url":"http://www.qqxxzx.com/images/pretty-girls-pictures/pretty-girls-pictures-17.jpg",
      "name":"Saansa",
      "age":20,
      "location":"Manhattan"
   },
   {  
      "url":"http://www.qqxxzx.com/images/pretty-girls-pictures/pretty-girls-pictures-18.jpg",
      "name":"Khaleesi",
      "age":24,
      "location":"Future Westros"
   },
   {  
      "url":"http://oddpad.com/wp-content/uploads/2015/05/pretty_girls_51.jpg",
      "name":"Heena",
      "age":21,
      "location":"India"
   },
   {  
      "url":"http://oddpad.com/wp-content/uploads/2014/12/pretty_girls_13.jpg",
      "name":"Jasmin",
      "age":23,
      "location":"Taxes"
   },
   {  
      "url":"http://www.spyderonlines.com/images/wallpapers/pretty-girls-pictures/pretty-girls-pictures-16.jpg",
      "name":"Monica",
      "age":23,
      "location":"New York"
   },
   {  
      "url":"http://cdn.cavemancircus.com//wp-content/uploads/images/2015/june/pretty_girls_3/pretty_girls_20.jpg",
      "name":"Phoebe",
      "age":22,
      "location":"New York City"
   },
   {  
      "url":"http://cdn.cavemancircus.com//wp-content/uploads/images/2015/march/pretty_girls_2/pretty_girls_12.jpg",
      "name":"Rachel",
      "age":22,
      "location":"Central Perk"
   },
   {  
      "url":"https://s-media-cache-ak0.pinimg.com/736x/de/87/7b/de877bcccc2295a58fe8758fee0ebc7d.jpg",
      "name":"Jainis",
      "age":24,
      "location":"New York"
   },
   {  
      "url":"https://scontent.cdninstagram.com/hphotos-xpf1/t51.2885-15/e15/10986280_404995676329336_1177563605_n.jpg",
      "name":"Samia",
      "age":19,
      "location":"India"
   },
   {  
      "url":"http://scontent-a.cdninstagram.com/hphotos-xaf1/t51.2885-15/10624158_694652173966291_65999198_n.jpg",
      "name":"Hornol",
      "age":24,
      "location":"Caltech"
   },
   {  
      "url":"http://i.imgur.com/wqsvWT4.jpg",
      "name":"Korial",
      "age":21,
      "location":"Japan"
   },
   {  
      "url":"http://data.whicdn.com/images/60103426/large.jpg",
      "name":"Chin Si",
      "age":23,
      "location":"China"
   },
   {  
      "url":"http://oddpad.com/wp-content/uploads/2015/03/pretty_girls_31.jpg",
      "name":"SDF",
      "age":21,
      "location":"America"
   }
]

Bước 7

Tạo file Utils.java

public class Utils {

    private static final String TAG = "Utils";

    public static List<Profile> loadProfiles(Context context){
        try{
            GsonBuilder builder = new GsonBuilder();
            Gson gson = builder.create();
            JSONArray array = new JSONArray(loadJSONFromAsset(context, "profiles.json"));
            List<Profile> profileList = new ArrayList<>();
            for(int i=0;i<array.length();i++){
                Profile profile = gson.fromJson(array.getString(i), Profile.class);
                profileList.add(profile);
            }
            return profileList;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    private static String loadJSONFromAsset(Context context, String jsonFileName) {
        String json = null;
        InputStream is=null;
        try {
            AssetManager manager = context.getAssets();
            Log.d(TAG,"path "+jsonFileName);
            is = manager.open(jsonFileName);
            int size = is.available();
            byte[] buffer = new byte[size];
            is.read(buffer);
            is.close();
            json = new String(buffer, "UTF-8");
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
        return json;
    }
}

Lưu ý

File này dùng để xử lý dữ liệu json và đổ vào model

Bước 8

Tạo đối tượng Profile

public class Profile {

    @SerializedName("name")
    @Expose
    private String name;

    @SerializedName("url")
    @Expose
    private String imageUrl;

    @SerializedName("age")
    @Expose
    private Integer age;

    @SerializedName("location")
    @Expose
    private String location;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public void setImageUrl(String imageUrl) {
        this.imageUrl = imageUrl;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

Lưu ý

  1. Chú thích @SerializedName thuộc về lớp Gson và được sử dụng để đọc biến tệp json và liên kết nó với Model
  2. Chú thích @Exposed chỉ ra rằng trường này nên được định nghĩa với JSON serialization hoặc deserialization.

Bước 9

Bây giờ chúng ta sẽ tạo một class để bind cardview và các phương thức của nó trong layout. Tạo fileTinderCard.java

@Layout(R.layout.tinder_card_view)
public class TinderCard {

    @View(R.id.profileImageView)
    private ImageView profileImageView;

    @View(R.id.nameAgeTxt)
    private TextView nameAgeTxt;

    @View(R.id.locationNameTxt)
    private TextView locationNameTxt;

    private Profile mProfile;
    private Context mContext;
    private SwipePlaceHolderView mSwipeView;

    public TinderCard(Context context, Profile profile, SwipePlaceHolderView swipeView) {
        mContext = context;
        mProfile = profile;
        mSwipeView = swipeView;
    }

    @Resolve
    private void onResolved(){
        Glide.with(mContext).load(mProfile.getImageUrl()).into(profileImageView);
        nameAgeTxt.setText(mProfile.getName() + ", " + mProfile.getAge());
        locationNameTxt.setText(mProfile.getLocation());
    }

    @SwipeOut
    private void onSwipedOut(){
        Log.d("EVENT", "onSwipedOut");
        mSwipeView.addView(this);
    }

    @SwipeCancelState
    private void onSwipeCancelState(){
        Log.d("EVENT", "onSwipeCancelState");
    }

    @SwipeIn
    private void onSwipeIn(){
        Log.d("EVENT", "onSwipedIn");
    }

    @SwipeInState
    private void onSwipeInState(){
        Log.d("EVENT", "onSwipeInState");
    }

    @SwipeOutState
    private void onSwipeOutState(){
        Log.d("EVENT", "onSwipeOutState");
    }
}

Lưu ý

  1. @layout được sử dụng để bind layout với lớp này.
  2. @View được sử dụng để bind các view trong layout mà chúng ta muốn tham chiếu.
  3. @Resolve để bind một phương thức sẽ được thực thi khi view đã sẵn sàng được sử dụng. Bất kỳ thao tác nào chúng ta muốn thực hiện trên các view tham chiếu nên được viết bằng một method và chú thích với điều này.
  4. @SwipeOut gọi phương thức khi cardview bị từ chối.
  5. @SwipeIn gọi phương thức khi cardviewdđược đồng ý.
  6. @SwipeCancelStated gọi phương thức chú thích khi thẻ được đặt lại chỗ cũ.
  7. @SwipeInState gọi phương thức chú thích cho đến khi thẻ di chuyển đến trạng thái được chấp nhận.
  8. @SwipeOutState gọi phương thức chú thích cho đến khi thẻ di chuyển đến trạng thái bị từ chối.
  9. Quan trọng: Nếu chúng ta không có kế hoạch để thêm lại một view thì class nên được chú thích bằng @NonReables để các tham chiếu được giải phóng và bộ nhớ được tối ưu hóa. Chúng ta sẽ thêm một view nếu bị từ chối, vì vậy chúng tôi đã không sử dụng @NonReUs.

Bước 10

Cuối cùng tạo file MainActivity.java để xử lý code.

public class MainActivity extends AppCompatActivity {

    private SwipePlaceHolderView mSwipeView;
    private Context mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSwipeView = (SwipePlaceHolderView)findViewById(R.id.swipeView);
        mContext = getApplicationContext();

        mSwipeView.getBuilder()
                .setDisplayViewCount(3)
                .setSwipeDecor(new SwipeDecor()
                        .setPaddingTop(20)
                        .setRelativeScale(0.01f)
                        .setSwipeInMsgLayoutId(R.layout.tinder_swipe_in_msg_view)
                        .setSwipeOutMsgLayoutId(R.layout.tinder_swipe_out_msg_view));


        for(Profile profile : Utils.loadProfiles(this.getApplicationContext())){
            mSwipeView.addView(new TinderCard(mContext, profile, mSwipeView));
        }

        findViewById(R.id.rejectBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mSwipeView.doSwipe(false);
            }
        });

        findViewById(R.id.acceptBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mSwipeView.doSwipe(true);
            }
        });
    }
}

Lưu ý

  1. Chúng ta có được ví dụ của SwipePlaceHolderView.
  2. Sau đó, chúng ta sử dụng phương thức getBuilder () để sửa đổi cấu hình view mặc định. Trong ví dụ này, chúng ta sẽ thêm 3 thẻ vào màn hình và tiếp tục thêm thẻ tiếp theo khi thẻ trên cùng bị xóa.
  3. Lớp SwipeDecor được sử dụng để điều chỉnh các yếu tố trực quan của view. Ở đây paddingTop và RelScale đưa ra nhận thức về một thẻ được đặt trong ngăn xếp. Thông báo cho thẻ đã nêu được thêm thông qua setSwipeInMsgLayoutId () và setSwipeOutMsgLayoutId ().
  4. Chúng ta load dữ liệu json và phân tích cú pháp vào Object và thêm nó vào danh sách SwipePlaceHolderView bằng phương thức addView ().
  5. Để lập trình thực hiện thao tác vuốt, chúng ta gọi phương thức doSwipe () với cờ để biểu thị thao tác vuốt được chấp nhận hoặc bị từ chối.

Nguồn tham khảo: https://blog.mindorks.com/android-tinder-swipe-view-example-3eca9b0d4794 Source code: https://github.com/janishar/Tutorials/tree/master/TinderSwipe