[Android 9.0 Pie Java] Implement horizontal swipe → dialog display → delete in chat application

environment

Android9.0 Pie Java

Introduction

I recently created a chat app in Android Java and implemented LINE-style chat list deletion. There are quite a few articles about the list display and deletion function of RecyclerView,

Swipe horizontally → Show dialog → Delete MainActivity + Multiple Fragment Configuration

I couldn't find the implementation example of in the Japanese article, so I will share it. (I don't know if it's a best practice, so please use it as a reference.

You can implement like this 243e5a5ccb079ad0d385004d69a930ba.gif

Finished product URL

The content is very long, so if you can't get the finished product, please click here. https://github.com/yuta-matsumoto/chat

Directory structure

Omit build files and manifest files

Chat  ├app/src/main/       ├java/       │ └com.example.chat/       │         ├fragments/       │         │    ├BaseFragment.java       │         │    ├ChatListFragment.java       │         │    └DeleteChatFragment.java       │         ├helpers/       │         │    ├ChatListAdapter.java       │         │    ├ViewHolder.java       │         │    └SwipeHelper.java       │         ├models/       │         │    ├ChatRowData.java       │         │    └DeleteChatRow.java       │         └MainAcitivity.java       ├res/       │ ├drawable/ │ │ └ sample1.png (Omitted below)       │ ├layout/       │ │   ├activity_main.xml       │ │   ├chat_list_row.xml       │ │   └fragment_chat_list.xml       │ └values/       │     ├colors.xml       │     ├strings.xml       │     └styles.xml

code

Dependencies

build.gradle


dependencies {
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0' //Postscript
    implementation 'androidx.cardview:cardview:1.0.0' //Postscript
}

Activity MainActivity only loads Fragment. Fragment inherits the base class of all Fragments called BaseFragment to realize MainActivity + multiple Fragment configurations.

I refer to the following articles. https://www.slideshare.net/olrandir/android-the-single-activity-multiple-fragments-pattern-one-activity-to-rule-them-all

MainActivity.java


package com.example.chat;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import com.example.chat.fragments.BaseFragment;
import com.example.chat.fragments.ChatListFragment;

public class MainActivity extends AppCompatActivity {
    //Fragment base
    private BaseFragment fragment;

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

        //Set Fragment of chat list for the first time
        if (fragment == null) {
            fragment = new ChatListFragment();
        }
        // main_Set Fragment to activity
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.mainContainer, fragment)
                .commit();
    }
}

Fragment

This is the base Fragment. I have not included it this time, but it is convenient to include the event listener of the common button in BaseFragment because it will be compact.

BaseFragment.java


package com.example.chat.fragments;

import android.os.Bundle;
import androidx.fragment.app.Fragment;

/**
 *Base Fragment
 */
public abstract class BaseFragment extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

Fragment of the chat list screen.

ChatListFragment.java


package com.example.chat.fragments;

import android.graphics.Color;
import android.os.Bundle;

import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.chat.models.DeleteChatRowData;
import com.example.chat.helpers.ChatListAdapter;
import com.example.chat.models.ChatRowData;
import com.example.chat.R;
import com.example.chat.helpers.SwipeHelper;

import java.util.ArrayList;
import java.util.List;

/**
 *Fragment for chat list screen
 */
public class ChatListFragment extends BaseFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_chat_list, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        RecyclerView rv = view.findViewById(R.id.recyclerView);

        //Chat list data List
        final List list = getChatList();
        //Number of elements in the data List of the chat list
        final int itemCount = list.size();

        //Chat list adapter
        final ChatListAdapter adapter = new ChatListAdapter(list) {
            @Override
            public void onItemClick(View view, int pos, List<ChatRowData> list) {
                //Added the process when clicking a line
            }
        };

        LinearLayoutManager llm = new LinearLayoutManager(getContext());

        rv.setHasFixedSize(true);

        rv.setLayoutManager(llm);

        rv.setAdapter(adapter);

        //Implement swipe
        SwipeHelper swipeHelper = new SwipeHelper(getContext(), rv) {
            @Override
            public void instantiateUnderlayButton(RecyclerView.ViewHolder viewHolder, List<UnderlayButton> underlayButtons) {
                underlayButtons.add(new SwipeHelper.UnderlayButton(
                        getResources().getString(R.string.chat_list_delete_button_label),
                        0,
                        Color.parseColor(getResources().getString(R.string.chat_list_delete_button_color)),
                        new SwipeHelper.UnderlayButtonClickListener() {
                            @Override
                            public void onClick(int pos) {
                                FragmentManager fragmentManager = getFragmentManager();
                                DeleteChatRowFragment fragment = new DeleteChatRowFragment();
                                //Set the row data to be deleted in the delete dialog fragment
                                DeleteChatRowData deleteChatRowData = new DeleteChatRowData();
                                deleteChatRowData.setList(list);
                                deleteChatRowData.setAdapter(adapter);
                                deleteChatRowData.setPosition(pos);
                                deleteChatRowData.setItemCount(itemCount);

                                //Pass data using bundle
                                Bundle bundle = new Bundle();
                                bundle.putSerializable(getResources().getString(R.string.delete_dialog_list_tag), deleteChatRowData);
                                fragment.setArguments(bundle);

                                //Dialog display
                                fragment.show(fragmentManager, "delete chat list");
                            }
                        }
                ));
            }
        };
    }

    /**
     *Chat list test data generation
     */
    private List<ChatRowData> getChatList() {
        List<ChatRowData> list = new ArrayList<>();

        ChatRowData data1 = new ChatRowData();
        data1.setName("Taro Tanaka");
        data1.setText("Hello");
        data1.setMessageDateTime("2020/6/09 13:00");
        data1.setProfileImageId(R.drawable.sample1);
        list.add(data1);

        ChatRowData data2 = new ChatRowData();
        data2.setName("Shigeru Sato");
        data2.setText("Good morning!");
        data2.setMessageDateTime("2020/6/08 8:10");
        data2.setProfileImageId(R.drawable.sample2);
        list.add(data2);

        ChatRowData data3 = new ChatRowData();
        data3.setName("taro");
        data3.setText("What time is it?");
        data3.setMessageDateTime("2020/6/07 20:09");
        data3.setProfileImageId(R.drawable.sample3);
        list.add(data3);

        ChatRowData data4 = new ChatRowData();
        data4.setName("hanako");
        data4.setText("Please lend me a textbook");
        data4.setMessageDateTime("2020/6/06 07:00");
        data4.setProfileImageId(R.drawable.sample4);
        list.add(data4);

        ChatRowData data5 = new ChatRowData();
        data5.setName("Tanaka");
        data5.setText("Impossible");
        data5.setMessageDateTime("2020/6/06 01:05");
        data5.setProfileImageId(R.drawable.sample5);
        list.add(data5);

        ChatRowData data6 = new ChatRowData();
        data6.setName("Kobayashi");
        data6.setText("OK");
        data6.setMessageDateTime("2020/6/05 14:22");
        data6.setProfileImageId(R.drawable.sample6);
        list.add(data6);

        ChatRowData data7 = new ChatRowData();
        data7.setName("Petagine");
        data7.setText("I want to go home");
        data7.setMessageDateTime("2020/6/05 13:00");
        data7.setProfileImageId(R.drawable.sample7);
        list.add(data7);

        ChatRowData data8 = new ChatRowData();
        data8.setName("Hayato");
        data8.setText("Let's go see the movie, senior!");
        data8.setMessageDateTime("2020/6/04 21:50");
        data8.setProfileImageId(R.drawable.sample8);
        list.add(data8);

        ChatRowData data9 = new ChatRowData();
        data9.setName("Tom");
        data9.setText("lol");
        data9.setMessageDateTime("2020/5/30 2:30");
        data9.setProfileImageId(R.drawable.sample9);
        list.add(data9);

        ChatRowData data10 = new ChatRowData();
        data10.setName("y.matsumoto");
        data10.setText("I did it");
        data10.setMessageDateTime("2020/5/29 4:00");
        data10.setProfileImageId(R.drawable.sample10);
        list.add(data10);

        return list;
    }
}

Fragment of the delete dialog.

DeleteChatRowFragment.java


package com.example.chat.fragments;

import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Gravity;
import android.widget.TextView;

import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.RecyclerView;

import com.example.chat.R;
import com.example.chat.models.DeleteChatRowData;

import java.util.List;

/**
 *Fragment for delete dialog
 */
public class DeleteChatRowFragment extends DialogFragment {
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // setCancelable(false)Does not close even if you press outside the dialog with
        this.setCancelable(false);
        TextView title = new TextView(getContext());
        title.setText(getResources().getString(R.string.delete_dialog_message));
        title.setPadding(10, 50, 10, 10);
        title.setGravity(Gravity.CENTER);

        return new AlertDialog.Builder(getActivity())
                .setCustomTitle(title)
                //When OK is pressed
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //Delete process
                        Bundle bundle = getArguments();
                        DeleteChatRowData deleteChatRowData =
                                (DeleteChatRowData) bundle.getSerializable(getResources().getString(R.string.delete_dialog_list_tag));
                        List list = deleteChatRowData.getList();
                        RecyclerView.Adapter adapter = deleteChatRowData.getAdapter();
                        int pos = deleteChatRowData.getPosition();
                        int itemCount = deleteChatRowData.getItemCount();
                        //Removed the element of the position order of the pressed line from the chat list List
                        list.remove(pos);
                        //Notify the adapter that the element has been deleted
                        deleteChatRowData.getAdapter().notifyItemRemoved(pos);
                        //Notify that there is a change in the chat list and rebind
                        adapter.notifyItemRangeChanged(pos, itemCount);
                    }
                })
                //When Cancel is pressed
                .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //Swipe back
                        Bundle bundle = getArguments();
                        DeleteChatRowData deleteChatRowData =
                                (DeleteChatRowData) bundle.getSerializable(getResources().getString(R.string.delete_dialog_list_tag));
                        RecyclerView.Adapter adapter = deleteChatRowData.getAdapter();
                        int pos = deleteChatRowData.getPosition();

                        //Swipe back
                        adapter.notifyItemChanged(pos);
                    }
                })
                .create();
    }

    //Don't quit when the app goes to the background
    @Override
    public void onPause() {
        super.onPause();
        dismiss();
    }
}

helper

Used to list chats.

ChatListAdapter.java


package com.example.chat.helpers;

import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import androidx.recyclerview.widget.RecyclerView;

import com.example.chat.R;
import com.example.chat.models.ChatRowData;

import java.util.List;

/**
 *Adapter class used to display chat list
 */
public class ChatListAdapter extends RecyclerView.Adapter<ViewHolder> {
    private List<ChatRowData> list;

    public ChatListAdapter(List<ChatRowData> list) {
        this.list = list;
    }

    /**
     *Create ViewHolder for chat list
     */
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //Load one line of layout into View
        View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_list_row, parent, false);
        final ViewHolder vh = new ViewHolder(inflate);

        //Register click listener
        inflate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Get the position of the clicked row
                int position = vh.getAdapterPosition();
                //Since the operation of View must be handled by Activity or Fragment, the actual processing is not written.
                onItemClick(v, position, list);
            }
        });

        //Register touch listener
        inflate.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //Since the operation of View must be handled by Activity or Fragment, the actual processing is not written.
                return onItemTouch(v);
            }
        });
        return vh;
    }

    /**
     *Bind the data of chat list List to View in ViewHolder
     */
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        String messageDateTime = list.get(position).getMessageDateTime();
        holder.nameView.setText(list.get(position).getName());
        holder.textView.setText(list.get(position).getText());
        holder.timeView.setText(messageDateTime);
        holder.profileView.setImageResource(list.get(position).getProfileImageId());
    }

    /**
     *Set the number of elements in the chat list List
     */
    @Override
    public int getItemCount() {
        return list.size();
    }

    /**
     *Override and process with ChatListFragment
     */
    public void onItemClick(View view, int pos, List<ChatRowData> list) {
        ;
    }

    /**
     *Override and process with ChatListFragment
     */
    public boolean onItemTouch(View view) {
        return false;
    }
}

ViewHolder.java


package com.example.chat.helpers;

import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

import com.example.chat.R;

/**
 *ViewHolder class
 *Define the View that makes up one line
 */
public class ViewHolder extends RecyclerView.ViewHolder {
    public TextView nameView;
    public TextView textView;
    public TextView timeView;
    public ImageView profileView;

    public ViewHolder(View itemView) {
        super(itemView);
        nameView = itemView.findViewById(R.id.name);
        textView = itemView.findViewById(R.id.text);
        timeView = itemView.findViewById(R.id.time);
        profileView = itemView.findViewById(R.id.profileImage);
    }
}

Used for horizontal swipe movement. I referred to this article quite a bit.

https://www.it-swarm.dev/ja/android/%E3%82%B9%E3%83%AF%E3%82%A4%E3%83%97%E3%81%AErecyclerview-itemtouchhelper%E3%83%9C%E3%82%BF%E3%83%B3/833735822/amp/

SwipeHelper.java


package com.example.chat.helpers;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

/**
 *Swipe helper class
 */
public abstract class SwipeHelper extends ItemTouchHelper.SimpleCallback {
    //DELETE button width displayed by swiping
    public static final int BUTTON_WIDTH = 230;
    private RecyclerView recyclerView;
    private List<UnderlayButton> buttons;
    private GestureDetector gestureDetector;
    private int swipedPos = -1;
    private float swipeThreshold = 0.5f;
    private Map<Integer, List<UnderlayButton>> buttonsBuffer;
    private Queue<Integer> recoverQueue;

    private GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            for (UnderlayButton button : buttons) {
                if (button.onClick(e.getX(), e.getY()))
                    break;
            }

            return true;
        }
    };

    private View.OnTouchListener onTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent e) {
            if (swipedPos < 0) return false;
            Point point = new Point((int) e.getRawX(), (int) e.getRawY());

            RecyclerView.ViewHolder swipedViewHolder = recyclerView.findViewHolderForAdapterPosition(swipedPos);
            View swipedItem = swipedViewHolder.itemView;
            Rect rect = new Rect();
            swipedItem.getGlobalVisibleRect(rect);

            if (e.getAction() == MotionEvent.ACTION_DOWN
                    || e.getAction() == MotionEvent.ACTION_UP
                    || e.getAction() == MotionEvent.ACTION_MOVE) {
                if (rect.top < point.y && rect.bottom > point.y)
                    gestureDetector.onTouchEvent(e);
                else {
                    recoverQueue.add(swipedPos);
                    swipedPos = -1;
                    recoverSwipedItem();
                }
            }
            return false;
        }
    };

    public SwipeHelper(Context context, RecyclerView recyclerView) {
        super(0, ItemTouchHelper.LEFT);
        this.recyclerView = recyclerView;
        this.buttons = new ArrayList<>();
        this.gestureDetector = new GestureDetector(context, gestureListener);
        this.recyclerView.setOnTouchListener(onTouchListener);
        buttonsBuffer = new HashMap<>();
        recoverQueue = new LinkedList<Integer>() {
            @Override
            public boolean add(Integer o) {
                if (contains(o))
                    return false;
                else
                    return super.add(o);
            }
        };

        attachSwipe();
    }


    @Override
    public boolean onMove(RecyclerView recyclerView,
                          RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        return false;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        int pos = viewHolder.getAdapterPosition();

        if (swipedPos != pos)
            recoverQueue.add(swipedPos);

        swipedPos = pos;

        if (buttonsBuffer.containsKey(swipedPos))
            buttons = buttonsBuffer.get(swipedPos);
        else
            buttons.clear();

        buttonsBuffer.clear();
        swipeThreshold = 0.5f * buttons.size() * BUTTON_WIDTH;
        recoverSwipedItem();
    }

    @Override
    public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
        return swipeThreshold;
    }

    @Override
    public float getSwipeEscapeVelocity(float defaultValue) {
        return 0.1f * defaultValue;
    }

    @Override
    public float getSwipeVelocityThreshold(float defaultValue) {
        return 5.0f * defaultValue;
    }

    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView,
                            RecyclerView.ViewHolder viewHolder,
                            float dX, float dY, int actionState, boolean isCurrentlyActive) {
        int pos = viewHolder.getAdapterPosition();
        float translationX = dX;
        View itemView = viewHolder.itemView;

        if (pos < 0) {
            swipedPos = pos;
            return;
        }

        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            if (dX < 0) {
                List<UnderlayButton> buffer = new ArrayList<>();

                if (!buttonsBuffer.containsKey(pos)) {
                    instantiateUnderlayButton(viewHolder, buffer);
                    buttonsBuffer.put(pos, buffer);
                } else {
                    buffer = buttonsBuffer.get(pos);
                }

                translationX = dX * buffer.size() * BUTTON_WIDTH / itemView.getWidth();
                drawButtons(c, itemView, buffer, pos, translationX);
            }
        }

        super.onChildDraw(c, recyclerView, viewHolder, translationX, dY, actionState, isCurrentlyActive);
    }

    private synchronized void recoverSwipedItem() {
        while (!recoverQueue.isEmpty()) {
            int pos = recoverQueue.poll();
            if (pos > -1) {
                recyclerView.getAdapter().notifyItemChanged(pos);
            }
        }
    }

    private void drawButtons(Canvas c, View itemView, List<UnderlayButton> buffer, int pos, float dX) {
        float right = itemView.getRight();
        float dButtonWidth = (-1) * dX / buffer.size();

        for (UnderlayButton button : buffer) {
            float left = right - dButtonWidth;
            button.onDraw(
                    c,
                    new RectF(
                            left,
                            itemView.getTop(),
                            right,
                            itemView.getBottom()
                    ),
                    pos
            );

            right = left;
        }
    }

    public void attachSwipe() {
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(this);
        itemTouchHelper.attachToRecyclerView(recyclerView);
    }

    public abstract void instantiateUnderlayButton(RecyclerView.ViewHolder viewHolder, List<UnderlayButton> underlayButtons);

    public static class UnderlayButton {
        private String text;
        private int imageResId;
        private int color;
        private int pos;
        private RectF clickRegion;
        private UnderlayButtonClickListener clickListener;

        public UnderlayButton(String text, int imageResId, int color, UnderlayButtonClickListener clickListener) {
            this.text = text;
            this.imageResId = imageResId;
            this.color = color;
            this.clickListener = clickListener;
        }

        public boolean onClick(float x, float y) {
            if (clickRegion != null && clickRegion.contains(x, y)) {
                clickListener.onClick(pos);
                return true;
            }

            return false;
        }

        public void onDraw(Canvas c, RectF rect, int pos) {
            Paint p = new Paint();

            //Background color set
            p.setColor(color);
            c.drawRect(rect, p);

            //DELETE text color set
            p.setColor(Color.WHITE);
            p.setTextSize(50);

            Rect r = new Rect();
            float cHeight = rect.height();
            float cWidth = rect.width();
            p.setTextAlign(Paint.Align.LEFT);
            p.getTextBounds(text, 0, text.length(), r);
            float x = cWidth / 2f - r.width() / 2f - r.left;
            float y = cHeight / 2f + r.height() / 2f - r.bottom;
            c.drawText(text, rect.left + x, rect.top + y, p);

            clickRegion = rect;
            this.pos = pos;
        }
    }

    public interface UnderlayButtonClickListener {
        void onClick(int pos);
    }
}

model

Fill the data for one line of the chat list.

ChatRowData.java


package com.example.chat.models;

/**
 *One line of data model class
 */
public class ChatRowData {
    private String name;
    private String text;
    private String messageDateTime;
    private int profileImageId;

    public String getName() {
        return name;
    }

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

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getMessageDateTime() {
        return messageDateTime;
    }

    public void setMessageDateTime(String messageDateTime) {
        this.messageDateTime = messageDateTime;
    }

    public int getProfileImageId() {
        return profileImageId;
    }

    public void setProfileImageId(int profileImageId) {
        this.profileImageId = profileImageId;
    }
}

Pack the data needed for the delete dialog.

ChatRowData.java


package com.example.chat.models;

/**
 *One line of data model class
 */
public class ChatRowData {
    private String name;
    private String text;
    private String messageDateTime;
    private int profileImageId;

    public String getName() {
        return name;
    }

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

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getMessageDateTime() {
        return messageDateTime;
    }

    public void setMessageDateTime(String messageDateTime) {
        this.messageDateTime = messageDateTime;
    }

    public int getProfileImageId() {
        return profileImageId;
    }

    public void setProfileImageId(int profileImageId) {
        this.profileImageId = profileImageId;
    }
}

Layout

Only FrameLayout for inserting Fragment is described in activity_main. Actually, if the header and footer are also made into common parts, maintainability will improve, but

-The point that the transition animation to the chat screen to be added (feeling that the chat screen layer covers the entire screen from the right) becomes difficult. ・ The point that I fell into hell when I combined the behavior of the back key

I forgot to make common parts. .. I would be really happy if I could hear about improvement measures for experts in the next article. ..

activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:focusableInTouchMode="true"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/mainContainer"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
    </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

This is the fragment layout for the chat list screen.

fragment_chat_list.xml


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:background="@color/background_light_gray"
    tools:context=".fragments.ChatListFragment">

    <View
        android:id="@+id/headerView"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:background="@color/background_dark_gray"
        android:contextClickable="false"
        android:layerType="none"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/chatListSubject"
        android:layout_width="63dp"
        android:layout_height="19dp"
        android:text="@string/chat_list_subject_label"
        android:textAlignment="center"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
        android:textColor="@color/font_color_black"
        app:layout_constraintBottom_toBottomOf="@+id/headerView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/headerView"
        app:layout_constraintVertical_bias="0.48" />

    <View
        android:id="@+id/headerBorder"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:layout_marginTop="48dp"
        android:background="@color/background_border"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/headerView" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:isScrollContainer="false"
        app:layout_constraintBottom_toTopOf="@+id/footerBorder"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/headerView"
        app:layout_constraintVertical_bias="0.0">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
    </LinearLayout>

    <View
        android:id="@+id/background"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:background="@color/background_dark_gray"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/footerBorder" />

    <ImageButton
        android:id="@+id/homeButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="80dp"
        android:background="@color/background_transparent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/footerBorder"
        app:srcCompat="@drawable/home" />

    <ImageButton
        android:id="@+id/chatListButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/background_transparent"
        app:layout_constraintEnd_toStartOf="@+id/userButton"
        app:layout_constraintStart_toEndOf="@+id/homeButton"
        app:layout_constraintTop_toTopOf="@+id/homeButton"
        app:srcCompat="@drawable/fukidashi" />

    <ImageButton
        android:id="@+id/userButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="80dp"
        android:background="@color/background_transparent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/chatListButton"
        app:srcCompat="@drawable/person" />

    <View
        android:id="@+id/footerBorder"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:layout_marginBottom="72dp"
        android:background="@color/background_border"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Chat list It is a layout for one line.

chat_list_row.xml


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.cardview.widget.CardView
        android:id="@+id/chatListCardView"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:cardCornerRadius="15dp"
        app:cardElevation="0dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:id="@+id/profileImage"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:srcCompat="@mipmap/ic_launcher" />
    </androidx.cardview.widget.CardView>

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:layout_marginStart="16dp"
        android:text="name"
        android:textColor="@color/font_color_black"
        android:textSize="12sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@+id/chatListCardView"
        app:layout_constraintTop_toTopOf="@+id/chatListCardView" />

    <TextView
        android:id="@+id/time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:text="time"
        android:textColor="@color/font_color_black"
        android:textSize="8sp"
        app:layout_constraintBottom_toBottomOf="@+id/name"
        app:layout_constraintStart_toEndOf="@+id/name" />

    <TextView
        android:id="@+id/text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="8dp"
        android:text="text"
        android:textColor="@color/font_color_black"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/name"
        app:layout_constraintTop_toBottomOf="@+id/name" />

    <View
        android:id="@+id/line"
        android:layout_width="match_parent"
        android:layout_height="0.3dp"
        android:layout_marginTop="8dp"
        android:background="@color/background_border"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/chatListCardView" />

</androidx.constraintlayout.widget.ConstraintLayout>

values This is the constant file to use

colors.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--Font color black-->
    <color name="font_color_black">#5E5E5E</color>
    <!--Font orange-->
    <color name="font_color_orange">#FF9900</color>
    <!-- background body -->
    <color name="background_light_gray">#EBEBEB</color>
    <!-- background header, footer -->
    <color name="background_dark_gray">#E5E5E5</color>
    <!--background separator-->
    <color name="background_border">#838383</color>
    <!--background transparent-->
    <color name="background_transparent">#00E5E5E5</color>
</resources>

strings.xml


<?xml version="1.0" encoding="utf-8"?>
<!--Constant management file-->
<resources>
    <!--app name-->
    <string name="app_name">Chat</string>
    <!--Chat list screen-->
    <string name="chat_list_subject_label">CHATS</string>
    <string name="chat_list_delete_button_color">#FF9900</string>
    <string name="chat_list_delete_button_label">DELETE</string>
    <string name="delete_dialog_message">Even if you delete this chat history\n Are you sure?</string>
    <string name="delete_dialog_list_tag">chatList</string>
</resources>


styles.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorAccent">@color/font_color_orange</item>
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>
</resources>


I think you can reproduce the movement of the sample at the beginning with the above code. The source is also available here, so please check it if you like. https://github.com/yuta-matsumoto/chat

Finally

I wrote a hands-on article for the first time, but I'm very confused about how much I should explain. .. I would be very happy if anyone could help!

Recommended Posts

[Android 9.0 Pie Java] Implement horizontal swipe → dialog display → delete in chat application
Display message dialog in java (personal memo)
[Android 9.0 Pie Java] Implement setOnTouchListener in the margin of RecyclerView and close the soft keyboard
[Android 9.0 Pie Java] LINE-style chat list screen → personal chat screen transition animation implemented (with sample application)
[Android] Review dialog display
Let's make a calculator application in Java ~ Display the application window
How to implement one-line display of TextView in Android development
Implement application function in Rails
Implement two-step verification in Java
Implement Basic authentication in Java
Implement math combinations in Java
2 Implement simple parsing in Java
[Android] Implement Adblock in WebView
Implement Email Sending in Java
Implement functional quicksort in Java
Implement rm -rf in Java.
Implement XML signature in Java
Source to display character array with numberPicker in Android studio (Java)
Let's create a TODO application in Java 5 Switch the display of TODO