天天看點

Android高仿iOS Messages聊天氣泡

Android高仿iOS Messages聊天氣泡

    • 一、目标
    • 二、功能分析
    • 三、實作代碼
      • 1. ChatItem
      • 2. DateItem
      • 3. TextItem
      • 4. PhotoItem
      • 5. ChatViewHolder
    • 四、開發過程回顧
    • 五、接下來
    • 六、Finally

在《iOS Messages顯示圖檔功能分析》一文中,我們總結了iOS Messages的氣泡形狀。現在着手實作之。

一、目标

實作iOS Messages聊天氣泡。

Android高仿iOS Messages聊天氣泡

二、功能分析

神馬筆記目前隻能發送資訊,不會收到資訊,是以隻考慮發送消息的氣泡形狀。

除了沒有氣泡外,共有6種氣泡形狀。

考慮消息時間的連續性,有4種——單獨、開始、中間、結束,使用4種氣泡形狀已經足夠。

同時考慮消息類型的連續型,同樣有4種——單獨、開始、中間、結束。

二者存在重疊情況,最終16種情況合并為6種氣泡形狀。

  • 氣泡形狀

尖角氣泡總是出現在對話告一段落的情況。

定義 描述
BUBBLE_NONE 沒有氣泡,如日期
BUBBLE_SINGLE 單個氣泡
BUBBLE_SINGLE_TAIL 單個尖角氣泡
BUBBLE_START 開始
BUBBLE_MIDDLE 中間
BUBBLE_END 結束氣泡
BUBBLE_END_TAIL 結束尖角氣泡
  • 時間類型
定義 描述
STYLE_SINGLE 單獨時間點
STYLE_START 開始時間點
STYLE_MIDDLE 中間時間點
STYLE_END 結束時間點
  • 消息類型
定義 描述
STYLE_SINGLE 單獨類型
STYLE_START 開始
STYLE_MIDDLE 中間
STYLE_END 結束
  • 關系表
時間類型 消息類型 氣泡類型
STYLE_SINGLE
STYLE_SINGLE BUBBLE_SINGLE_TAIL
STYLE_START BUBBLE_SINGLE_TAIL
STYLE_MIDDLE BUBBLE_SINGLE_TAIL
STYLE_END BUBBLE_SINGLE_TAIL
STYLE_START
STYLE_SINGLE BUBBLE_SINGLE
STYLE_START BUBBLE_START
STYLE_MIDDLE BUBBLE_START
STYLE_END BUBBLE_SINGLE
STYLE_MIDDLE
STYLE_SINGLE BUBBLE_SINGLE
STYLE_START BUBBLE_START
STYLE_MIDDLE BUBBLE_MIDDLE
STYLE_END BUBBLE_END
STYLE_END
STYLE_SINGLE BUBBLE_SINGLE_TAIL
STYLE_START BUBBLE_SINGLE_TAIL
STYLE_MIDDLE BUBBLE_END_TAIL
STYLE_END BUBBLE_END_TAIL

三、實作代碼

1. ChatItem

方法 描述 依賴
public int getBubble() 擷取氣泡形狀

getTimeStyle()

getTypeStyle()

protected int getTimeStyle() 擷取時間類型 isTimeContinuous()
protected int getTypeStyle() 擷取消息類型 isTypeContinuous()
protected boolean isTimeContinuous(ChatItem item) 判斷時間是否連續,目前設定為3分鐘。
protected boolean isTypeContinuous(ChatItem item) 判斷類型是否連續
public class ChatItem<E extends MessageEntity> {

    public static final int TYPE_NONE   = 0;
    public static final int TYPE_DATE   = 1;
    public static final int TYPE_TEXT   = 2;
    public static final int TYPE_PHOTO  = 3;

    public static final int BUBBLE_NONE         = 0;
    public static final int BUBBLE_SINGLE       = 1;
    public static final int BUBBLE_SINGLE_TAIL  = 2;
    public static final int BUBBLE_START        = 3;
    public static final int BUBBLE_MIDDLE       = 4;
    public static final int BUBBLE_END          = 5;
    public static final int BUBBLE_END_TAIL     = 6;

    public static final int STYLE_SINGLE    = 0;
    public static final int STYLE_START     = 1;
    public static final int STYLE_MIDDLE    = 2;
    public static final int STYLE_END       = 3;


    int type;
    E entity;
    protected ChatProvider parent;

    public ChatItem(ChatProvider parent, E entity, int type) {
        this.parent = parent;
        this.entity = entity;
        this.type = type;
    }

    public E getEntity() {
        return entity;
    }

    public int getType() {
        return this.type;
    }

    public DateTime getCreated() {
        return entity.getCreated();
    }

    public int getBubble() {
        int bubble = BUBBLE_SINGLE_TAIL;

        int time = getTimeStyle();
        int type = getTypeStyle();

        switch (time) {
            case STYLE_SINGLE: {
                bubble = BUBBLE_SINGLE_TAIL;
                break;
            }
            case STYLE_START: {
                if (type == STYLE_SINGLE) {
                    bubble = BUBBLE_SINGLE;
                } else if (type == STYLE_START) {
                    bubble = BUBBLE_START;
                } else if (type == STYLE_MIDDLE) {
                    bubble = BUBBLE_START;
                } else if (type == STYLE_END) {
                    bubble = BUBBLE_SINGLE;
                }
                break;
            }
            case STYLE_MIDDLE: {
                if (type == STYLE_SINGLE) {
                    bubble = BUBBLE_SINGLE;
                } else if (type == STYLE_START) {
                    bubble = BUBBLE_START;
                } else if (type == STYLE_MIDDLE) {
                    bubble = BUBBLE_MIDDLE;
                } else if (type == STYLE_END) {
                    bubble = BUBBLE_END;
                }
                break;
            }
            case STYLE_END: {
                if (type == STYLE_SINGLE) {
                    bubble = BUBBLE_SINGLE_TAIL;
                } else if (type == STYLE_START) {
                    bubble = BUBBLE_SINGLE_TAIL;
                } else if (type == STYLE_MIDDLE) {
                    bubble = BUBBLE_END_TAIL;
                } else if (type == STYLE_END) {
                    bubble = BUBBLE_END_TAIL;
                }
                break;
            }
        }



        return bubble;
    }

    protected int getTimeStyle() {
        boolean pre = false;
        boolean next = false;

        int position = parent.indexOf(this);
        if (position > 0) {
            pre = isTimeContinuous(parent.get(position - 1));
        }
        if (position >= 0 && position < (parent.size() - 1)) {
            next = isTimeContinuous(parent.get(position + 1));
        }

        int style = STYLE_SINGLE;
        if (pre && next) {
            style = STYLE_MIDDLE;
        } else if (pre && !next) {
            style = STYLE_END;
        } else if (!pre && next) {
            style = STYLE_START;
        } else if (!pre && !next) {
            style = STYLE_SINGLE;
        }

        return style;
    }

    protected int getTypeStyle() {
        boolean pre = false;
        boolean next = false;

        int position = parent.indexOf(this);
        if (position > 0) {
            pre = isTypeContinuous(parent.get(position - 1));
        }
        if (position >= 0 && position < (parent.size() - 1)) {
            next = isTypeContinuous(parent.get(position + 1));
        }

        int style = STYLE_SINGLE;
        if (pre && next) {
            style = STYLE_MIDDLE;
        } else if (pre && !next) {
            style = STYLE_END;
        } else if (!pre && next) {
            style = STYLE_START;
        } else if (!pre && !next) {
            style = STYLE_SINGLE;
        }

        return style;
    }

    protected boolean isTypeContinuous(ChatItem item) {
        return this.getType() == item.getType();
    }

    protected boolean isTimeContinuous(ChatItem item) {
        return isTimeContinuous(this, item, 3);
    }

    static boolean isTimeContinuous(ChatItem current, ChatItem next, int minutes) {
        DateTime date = current.getCreated();
        DateTime now = next.getCreated();

        int diff = Minutes.minutesBetween(date, now).getMinutes();
        diff = Math.abs(diff);
        boolean result = (diff < minutes);

        return result;
    }
}

           

2. DateItem

沒有氣泡。

public class DateItem extends ChatItem<MessageEntity> {

    public DateItem(ChatProvider parent, MessageEntity entity) {
        super(parent, entity, TYPE_DATE);
    }

    @Override
    public int getBubble() {
        return BUBBLE_NONE;
    }
}

           

3. TextItem

重載

getTypeStyle

方法,文本消息類型總看作單獨的。

public class TextItem extends ChatItem<TextEntity> {

    public TextItem(ChatProvider parent, TextEntity entity) {
        super(parent, entity, TYPE_TEXT);
    }

    @Override
    protected int getTypeStyle() {
        return STYLE_SINGLE;
    }
    
    public String getText() {
        return entity.getText();
    }

}

           

4. PhotoItem

未來在判斷類型連續性上會進行擴充,重載之。

public class PhotoItem extends ChatItem<PhotoEntity> {

    public PhotoItem(ChatProvider parent, PhotoEntity entity) {
        super(parent, entity, TYPE_PHOTO);
    }

    @Override
    protected boolean isTypeContinuous(ChatItem item) {
        return super.isTypeContinuous(item);
    }

    public int getWidth() {
        return entity.getWidth();
    }

    public int getHeight() {
        return entity.getHeight();
    }

    public Uri getUri() {
        return entity.getUri();
    }

    public File getFile() {
        return entity.getFile();
    }

    public String getSignature() {
        return entity.getSignature();
    }
}

           

5. ChatViewHolder

根據氣泡形狀,擷取對應的圖檔資源。

public abstract class ChatViewHolder<E extends ChatItem> extends BridgeViewHolder<E> {

    ViewStub viewStub;

    @Keep
    public ChatViewHolder(View itemView) {
        super(itemView);
    }

    @Override
    public void onViewCreated(@NonNull View view) {
        this.viewStub = view.findViewById(R.id.stub);
    }

    public int getBubble(ChatItem item) {
        int resId = R.drawable.ic_outgoing_bubble_single_tail;
        switch (item.getBubble()) {
            case ChatItem.BUBBLE_NONE: {
                resId = 0;
                break;
            }
            case ChatItem.BUBBLE_SINGLE: {
                resId = R.drawable.ic_outgoing_bubble_single;
                break;
            }
            case ChatItem.BUBBLE_SINGLE_TAIL: {
                resId = R.drawable.ic_outgoing_bubble_single_tail;
                break;
            }
            case ChatItem.BUBBLE_START: {
                resId = R.drawable.ic_outgoing_bubble_start;
                break;
            }
            case ChatItem.BUBBLE_MIDDLE: {
                resId = R.drawable.ic_outgoing_bubble_middle;
                break;
            }
            case ChatItem.BUBBLE_END: {
                resId = R.drawable.ic_outgoing_bubble_end;
                break;
            }
            case ChatItem.BUBBLE_END_TAIL: {
                resId = R.drawable.ic_outgoing_bubble_end_tail;
                break;
            }
        }

        return resId;
    }
}

           

四、開發過程回顧

從6種氣泡形狀開始,發現決定氣泡形狀的2個參數——時間類型、消息類型。

根據時間類型和消息類型組合出對應管理。

再根據時間和類型的連續性計算出對應的類型。

進而最終計算出每條消息對應的氣泡形狀。

五、接下來

組合所有功能,實作神馬筆記在對話中插入圖檔消息。

六、Finally

~若是經典所在之處~即為有佛~若尊重弟子~