Recently, IM chat function was used in the project. Since the project does not plan to integrate third-party SDK, I wrote a UI interface to realize message sending and receiving. If you need to move to your own project, you can show the implementation first, and then a brief description of how it is implemented:

1. Overall layout The layout is divided into three parts: the chat list, the layout where the input box is, and the layout where the bottom emoticons and other message options are located 1.1 Chat list: here is SwipeRefreshLayout and RecyclerView, HERE I use Google’s official drop-down refresh control SwipeRefreshLayout to achieve the drop-down refresh to obtain historical messages, if you want other styles to replace the drop-down refresh control on the line.

1.2 Layout of input box and expression and more options: there are mainly several parts here, voice button, expression button, plus sign button, input box, send button, voice press button, expression layout, more layout, keyboard, we can control the display and hide of these controls in the code. I’ll take clicking the plus button as an example:

// Bind bottom plus button public ChatUiHelperbindToAddButton(View addButton) {
        mAddButton = addButton;
        addButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mEditText.clearFocus(); // Hide long press send voice button hideAudioButton(); // Emoticons and more are displayed in the layoutif(mbottomLayout.isshown ()){// More information about the layout is displayedif(maddlayout.isshown ()){// When displaying the software disk, lock the content height to prevent skipping. lockContentHeight(); // Hide more layouts to show the software disk hideBottomLayout(true); / / software disk display after release content height unlockContentHeightDelayed (); }else{// Show more layouts showMoreLayout(); // hideEmotionLayout(); }}else{// More layout is not displayed, keyboard displayif (isSoftInputShown()) {
                      hideEmotionLayout();
                      showMoreLayout();
                      lockContentHeight();
                      showBottomLayout();
                      unlockContentHeightDelayed();
                  } elseShowMoreLayout (); hideEmotionLayout(); showBottomLayout(); }}}});return this;
    }
Copy the code

1.3 RecycleView Layout displayed by different items: THE chat interface I wrote mainly includes text message, voice message, picture message, video message and file message. Modify directly to the corresponding item layout can be modified. For more than the layout of the display I use BaseRecyclerViewAdapterHelper, a type corresponds to a layout. After sending, it can be displayed according to different types. After sending the network request, it can update a single item.

    private static final int SEND_TEXT = R.layout.item_text_send;
    private static final int RECEIVE_TEXT = R.layout.item_text_receive;
    private static final int SEND_IMAGE = R.layout.item_image_send;
    private static final int RECEIVE_IMAGE = R.layout.item_image_receive;
    private static final int SEND_VIDEO = R.layout.item_video_send;
    private static final int RECEIVE_VIDEO = R.layout.item_video_receive;
    private static final int SEND_FILE = R.layout.item_file_send;
    private static final int RECEIVE_FILE = R.layout.item_file_receive;
    private static final int RECEIVE_AUDIO = R.layout.item_audio_receive;
    private static final int SEND_AUDIO = R.layout.item_audio_send;
Copy the code

2. Expression function realization

2.1
2.2
Unicode
Unicode.org/emoji/chart…
2.3

public  ChatUiHelper bindEmojiData(){// get all emojis mListEmoji = emojidao.getInstance ().getemojibean (); LinearLayout homeEmoji = (LinearLayout)mActivity.findViewById(R.i.Haome_emoji); Viewpager viewpager vpEmoji = (viewpager) mactivity.findViewById (R.i.Vp_emoji); Final IndicatorView indEmoji = (IndicatorView) mactivity.findViewById (r.id_emoji); // Final IndicatorView indEmoji = (IndicatorView) mactivity.findViewById (R.id_emoji); LayoutInflater inflater = LayoutInflater.from(mActivity); Int pageSize = EVERY_PAGE_SIZE; MEmojiBean =new EmojiBean(); mEmojiBean.setId(0); mEmojiBean.setUnicodeInt(000); Int deleteCount= (int) math.ceil (mlistemoji.size () * 1.0/EVERY_PAGE_SIZE); // The number of delete keys to display logutil.d""+deleteCount); // Add delete keyfor(int i=1; i<deleteCount+1; i++){if(i==deleteCount){
                mListEmoji.add( mListEmoji.size(),mEmojiBean);
            }else{
                mListEmoji.add(i*EVERY_PAGE_SIZE-1,mEmojiBean);
            }
            LogUtil.d("Add times"+i); } // count the pageCount int pageCount = (int) math.ceil ((mlistemoji.size ()) * 1.0 / pageSize); // Total pages logutil.d ("Total pages :"+pageCount); // create a recycleView List<View> viewList = new ArrayList<View>();for (int index = 0; index < pageCount; index++) {
            RecyclerView recyclerView = (RecyclerView) inflater.inflate(R.layout.item_emoji_vprecy, vpEmoji, false);
            recyclerView.setLayoutManager( new GridLayoutManager(mActivity, 7));
            EmojiAdapter entranceAdapter;
           if(index== pagecount-1){// If the last page of the adapter is passed into the data List<EmojiBean> lastPageList=mListEmoji.subList(index*EVERY_PAGE_SIZE,mListEmoji.size()); entranceAdapter = new EmojiAdapter( lastPageList , index, EVERY_PAGE_SIZE); }elseEntranceAdapter = new EmojiAdapter(mlistemoji. subList(index*EVERY_PAGE_SIZE, (index+1)*EVERY_PAGE_SIZE), index, EVERY_PAGE_SIZE); } / / expressions the click event of entranceAdapter. SetOnItemClickListener (new BaseQuickAdapter.OnItemClickListener() {
                @Override
                public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
                     EmojiBean mEmojiBean=(EmojiBean)adapter.getData().get(position);
                    if(mEmojiBean getId () = = 0) {/ / if the key is to remove mEditText dispatchKeyEvent (new KeyEvent (KeyEvent. ACTION_DOWN, KeyEvent. KEYCODE_DEL));  }else{ mEditText.append(((EmojiBean)adapter.getData().get(position)).getUnicodeInt()); }}}); // Add data source recyclerView.setAdapter(entranceAdapter) for each recycleView; viewList.add(recyclerView); } // Set viewpager and indicator EmojiVpAdapter adapter = new EmojiVpAdapter(viewList); vpEmoji.setAdapter(adapter); indEmoji.setIndicatorCount(vpEmoji.getAdapter().getCount()); indEmoji.setCurrentIndicator(vpEmoji.getCurrentItem()); vpEmoji.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { indEmoji.setCurrentIndicator(position); }});returnthis; }Copy the code

3. Realization of recording function

3.1

public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        y = event.getY();
        if(mStateTV! =null && mStateIV! =null &&y<0){ mStateTV.setText("Unsend by releasing your finger.");
            mStateIV.setImageDrawable(getResources().getDrawable(R.drawable.ic_volume_cancel));
        }else if(mStateTV ! = null){ mStateTV.setText("Swipe up and cancel send.");
        }
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                setText("Release send");
                initDialogAndStartRecord();
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                this.setText("Hold the recording.");
                 if(y>=0 && (System.currentTimeMillis() - startTime <= MAX_INTERVAL_TIME)){
                     finishRecord();
                }else if(y<0){  
                    cancelRecord();
                }
                break;
        }

        return true;
    }
Copy the code

3.2 Display of Dialog: This section mainly deals with the display of animation and the replacement of pictures in different states. When you initialize a Dialog, you set ImageDrawable to enable animation for ImageViews, and then display different images in the middle of the Dialog depending on the operation.

mStateIV.setImageDrawable(getResources().getDrawable(R.drawable.anim_mic)); anim = (AnimationDrawable) mStateIV.getDrawable(); anim.start(); ----------------------------------- mStateIV.setImageDrawable(getResources().getDrawable(R.drawable.ic_volume_wraning));  mStateTV.setText("The recording time is too short.");
 anim.stop();
Copy the code

3.3 recording function: recording through the MediaRecorder class, in Android we can through the MediaRecorder to record audio, divided into the following steps: 1, create MediaRecorder instance object. 2, setAudioSource (int source) method to set up the voice, the inside of the parameters of the source is set to the MediaRecorder. AudioSource. MIC, this parameter specifies the audio source is given priority to the microphone. 3. Call setOutputFormat(int output_format) to set the format of the output file generated during recording. 4, set up the coding format MediaRecord. SetAudioEncoder (MediaRecorder. AudioEncoder. The AMR_NB); // Set the audio encoding to AMr_nb. 5, set the recording audio file to save the location, by calling the MediaRecorder object setOutputFile(String path) method,path transmission path can be. 6. Call the Prepare () method of MediaRecorder to prepare for recording. 7, call the Start () method of the MediaRecorder object to start recording. 8, after recording, call the Stop () method of the MediaRecorder object to stop recording, and call the release() method to release resources. I saved the recorded files in the data/data/ package name /files directory.

private void startRecording() {
        if(mRecorder ! = null) { mRecorder.reset(); }else {
            mRecorder = new MediaRecorder();
        }
        mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        File file = new File(mFile);
        LogUtil.d("Create file path :"+mFile);
         mRecorder.setOutputFile(mFile);
         try {
            mRecorder.prepare();
            mRecorder.start();
        }catch (Exception e){
            LogUtil.d("Preparestart abnormal, restart recording :"+e.toString()); e.printStackTrace(); mRecorder.release(); mRecorder = null ; startRecording(); }}Copy the code

3.4 Click Play in RecycleView, we can play the animation of item through MediaPlayer at the same time. Cancels multiple plays based on whether the variable is null when clicked. The initialization of MediaPlayer is similar to that of MediaRecorder for playing audio, as shown in the following code:

/ / item. Click the play mAdapter setOnItemChildClickListener (new BaseQuickAdapter.OnItemChildClickListener() {
            @Override
            public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
                if(ivAudio ! = null) { ivAudio.setBackgroundResource(R.mipmap.audio_animation_list_right_3); ivAudio = null; MediaManager.reset(); }else{
                     ivAudio = view.findViewById(R.id.ivAudio);
                      MediaManager.reset();
                      ivAudio.setBackgroundResource(R.drawable.audio_animation_right_list);
                     AnimationDrawable  drawable = (AnimationDrawable) ivAudio.getBackground();
                    drawable.start();
                     MediaManager.playSound(ChatActivity.this,((AudioMsgBody)mAdapter.getData().get(position).getBody()).getLocalPath(), new MediaPlayer.OnCompletionListener() {
                        @Override
                        public void onCompletion(MediaPlayer mp) {
                            LogUtil.d("Start play end"); ivAudio.setBackgroundResource(R.mipmap.audio_animation_list_right_3); MediaManager.release(); }}); }}}); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / / play audio public static void playSound (Context to Context ,String filePathString, OnCompletionListener onCompletionListener) { try { filepathstrings = filePathString ; mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mPlayer.setOnCompletionListener(onCompletionListener); mPlayer.setDataSource(filePathString); MPlayer. SetVolume (90, living); mPlayer.setLooping(false);
            mPlayer.prepare();
            mPlayer.start();
            isStart = true; } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }}Copy the code

4. More layout features

Some of the UI cutout in the code is ported from sealTalk in the cloud github.com/sealtalk/se… Pictures and video to choose what I use PictureSelector:github.com/LuckSiege/P… The choice of file I use MaterialFilePicker:github.com/nbsp-team/M… Many layout shows BaseRecyclerViewAdapterHelper:github.com/CymChad/Bas… You can download the github address directly from github.com/huangruiLea…