微信聊天窗口的信息效果类似iphone上的短信效果,以气泡的形式展现,在Android上,实现这种效果主要用到ListView和BaseAdapter,配合布局以及相关素材,就可以自己做出这个效果,素材可以下一个微信的APK,然后把后缀名改成zip,直接解压,就可以得到微信里面的所有素材了。首先看一下我实现的效果:
以下是工程目录结构:
接下来就是如何实现这个效果的代码:
main.xml,这个是主布局文件,显示listview和上下两部分内容。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#f0f0e0" >
<RelativeLayout
android:id="@+id/rl_top"
android:layout_width="fill_parent"
android:layout_alignParentTop="true"
android:layout_height="wrap_content">
<TextView
android:layout_width="fill_parent"
android:layout_height="44dp"
android:gravity="center"
android:textSize="18sp"
android:background="#486a9a"
android:textColor="@android:color/white"
android:text="Chat"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/rl_bottom"
android:layout_alignParentBottom="true"
android:layout_width="fill_parent"
android:background="#486a9a"
android:paddingTop="5dp"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn_send"
android:layout_width="70dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:text="Send" />
<EditText
android:id="@+id/et_content"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_toLeftOf="@id/btn_send"
android:textSize="16sp"/>
</RelativeLayout>
<ListView
android:id="@+id/listview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="@id/rl_bottom"
android:layout_below="@id/rl_top"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="10dp"
android:cacheColorHint="#00000000"
android:divider="@null"
android:listSelector="#00000000"
android:dividerHeight="3dp"
android:scrollbars="none"/>
</RelativeLayout>
然后就是listview中两种类型item的布局文件,分别是接收信息的item效果和发送信息的item效果
chat_from_item.xml是接收信息的item布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:orientation="vertical"
android:paddingBottom="5dp"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="#bfbfbf"
android:paddingTop="2dp"
android:paddingBottom="2dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="#ffffff"
android:textSize="12sp" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp" >
<ImageView
android:id="@+id/iv_user_image"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:background="@drawable/mypic"
android:focusable="false" />
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_toRightOf="@+id/iv_user_image"
android:background="@drawable/chatfrom_bg"
android:gravity="left|center"
android:clickable="true"
android:focusable="true"
android:lineSpacingExtra="2dp"
android:minHeight="50dp"
android:textColor="#ff000000"
android:textSize="14sp" />
</RelativeLayout>
</LinearLayout>
chat_to_item.xml是发送信息item的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:orientation="vertical"
android:paddingBottom="5dp"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#bfbfbf"
android:layout_gravity="center_horizontal"
android:paddingTop="2dp"
android:paddingBottom="2dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="#ffffff"
android:textSize="12sp" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp" >
<ImageView
android:id="@+id/iv_user_image"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:background="@drawable/mypic"
android:focusable="false" />
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@+id/iv_user_image"
android:background="@drawable/chatto_bg"
android:gravity="left|center"
android:clickable="true"
android:focusable="true"
android:lineSpacingExtra="2dp"
android:textColor="#ff000000"
android:textSize="14sp" />
</RelativeLayout>
</LinearLayout>
布局完成后新建一个实体类ChatEntity.java:
public class ChatEntity {
private int userImage;
private String content;
private String chatTime;
private boolean isComeMsg;
public int getUserImage() {
return userImage;
}
public void setUserImage(int userImage) {
this.userImage = userImage;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getChatTime() {
return chatTime;
}
public void setChatTime(String chatTime) {
this.chatTime = chatTime;
}
public boolean isComeMsg() {
return isComeMsg;
}
public void setComeMsg(boolean isComeMsg) {
this.isComeMsg = isComeMsg;
}
}
最后就是主Activity,这里面包括了自己写的BaseAdapter:
public class ChatDemoActivity extends Activity {
private Button sendButton = null;
private EditText contentEditText = null;
private ListView chatListView = null;
private List<ChatEntity> chatList = null;
private ChatAdapter chatAdapter = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
游戏开发论坛:http://jiushun8.com/forum.php?mod=viewthread&tid=4371&extra=page%3D1
setContentView(R.layout.main);
contentEditText = (EditText) this.findViewById(R.id.et_content);
sendButton = (Button) this.findViewById(R.id.btn_send);
chatListView = (ListView) this.findViewById(R.id.listview);
chatList = new ArrayList<ChatEntity>();
ChatEntity chatEntity = null;
for (int i = 0; i < 2; i++) {
chatEntity = new ChatEntity();
if (i % 2 == 0) {
chatEntity.setComeMsg(false);
chatEntity.setContent("Hello");
chatEntity.setChatTime("2012-09-20 15:12:32");
}else {
chatEntity.setComeMsg(true);
chatEntity.setContent("Hello,nice to meet you!");
chatEntity.setChatTime("2012-09-20 15:13:32");
}
chatList.add(chatEntity);
}
chatAdapter = new ChatAdapter(this,chatList);
chatListView.setAdapter(chatAdapter);
sendButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!contentEditText.getText().toString().equals("")) {
//发送消息
send();
}else {
Toast.makeText(ChatDemoActivity.this, "Content is empty", Toast.LENGTH_SHORT).show();
}
}
});
}
private void send(){
ChatEntity chatEntity = new ChatEntity();
chatEntity.setChatTime("2012-09-20 15:16:34");
chatEntity.setContent(contentEditText.getText().toString());
chatEntity.setComeMsg(false);
chatList.add(chatEntity);
chatAdapter.notifyDataSetChanged();
chatListView.setSelection(chatList.size() - 1);
contentEditText.setText("");
}
private class ChatAdapter extends BaseAdapter{
private Context context = null;
private List<ChatEntity> chatList = null;
private LayoutInflater inflater = null;
private int COME_MSG = 0;
private int TO_MSG = 1;
public ChatAdapter(Context context,List<ChatEntity> chatList){
this.context = context;
this.chatList = chatList;
inflater = LayoutInflater.from(this.context);
}
@Override
public int getCount() {
return chatList.size();
}
@Override
public Object getItem(int position) {
return chatList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getItemViewType(int position) {
// 区别两种view的类型,标注两个不同的变量来分别表示各自的类型
ChatEntity entity = chatList.get(position);
if (entity.isComeMsg())
{
return COME_MSG;
}else{
return TO_MSG;
}
}
@Override
public int getViewTypeCount() {
// 这个方法默认返回1,如果希望listview的item都是一样的就返回1,我们这里有两种风格,返回2
return 2;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ChatHolder chatHolder = null;
if (convertView == null) {
chatHolder = new ChatHolder();
if (chatList.get(position).isComeMsg()) {
convertView = inflater.inflate(R.layout.chat_from_item, null);
}else {
convertView = inflater.inflate(R.layout.chat_to_item, null);
}
chatHolder.timeTextView = (TextView) convertView.findViewById(R.id.tv_time);
chatHolder.contentTextView = (TextView) convertView.findViewById(R.id.tv_content);
chatHolder.userImageView = (ImageView) convertView.findViewById(R.id.iv_user_image);
convertView.setTag(chatHolder);
}else {
chatHolder = (ChatHolder)convertView.getTag();
}
chatHolder.timeTextView.setText(chatList.get(position).getChatTime());
chatHolder.contentTextView.setText(chatList.get(position).getContent());
chatHolder.userImageView.setImageResource(chatList.get(position).getUserImage());
return convertView;
}
private class ChatHolder{
private TextView timeTextView;
private ImageView userImageView;
private TextView contentTextView;
}
}
}
源代码下载:ChatDemo
对Android&IOS感兴趣的朋友可以加入我们的讨论QQ群,在这里,我们只讨论干货:
iOS群:220223507
Android群:282552849
游戏开发论坛:http://jiushun8.com/forum.php?mod=viewthread&tid=4371&extra=page%3D1
本项目代码已经上传至CSDN资源下载板块 http://download.csdn.net/detail/liuguangzhou123/5175389
模型主要代码如下:
//TreeModel.h
#ifndef TREEMODEL_H
#define TREEMODEL_H
#include <QAbstractItemModel>
#include <QStandardItemModel>
#include "DevRoot.h"
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
TreeModel(QObject *parent = 0);
~TreeModel();
QVariant data(const QModelIndex &index, int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &index) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
private:
void setupModelData(/*const QStringList &lines, TreeItem *parent*/);
CDevRoot *m_pDevRoot;
};
#endif // TREEMODEL_H
//TreeModel.cpp
#include "TreeModel.h"
#include <QStringList>
#include <qDebug>
#include "NodeAA.h"
#include "NodeBB.h"
#include "NodeCC.h"
TreeModel::TreeModel(QObject *parent)
: QAbstractItemModel(parent)
{
m_pDevRoot = new CDevRoot("根目录");
setupModelData();
}
TreeModel::~TreeModel()
{
delete m_pDevRoot;
}
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
else if (role == Qt::DisplayRole)
{
CNodeBase *item = static_cast<CNodeBase*>(index.internalPointer());
return item->getName();
}
else if(role == Qt::DecorationRole)
{
CNodeBase *item = static_cast<CNodeBase*>(index.internalPointer());
if(item->getNodeType() == MAX_LEN_NODE_NAME)
return QIcon(":/images/window.bmp");
return QIcon(":/images/test.png");
}
else if(role == Qt::CheckStateRole)
{
}
else if(role == Qt::UserRole)
{
}
return QVariant();
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return m_pDevRoot->getName();
return QVariant();
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
CNodeBase *pNodeBase;
if (!parent.isValid())//ROOT下挂装置NodeMac
{
CNodeBase *pNodeMac = m_pDevRoot->getNodeMac(row);
return createIndex(row, column, pNodeMac);
}
pNodeBase = static_cast<CNodeBase*>(parent.internalPointer());
if (pNodeBase->getNodeType() == NODE_TYPE_MAC && row == 0)//装置NodeMac下挂装置AA
{
CNodeMac *pNodeMac = static_cast<CNodeMac*>(parent.internalPointer());
CNodeAA *P = pNodeMac->getChildDeviceAA();
return createIndex(row,column,P);
}
else if (pNodeBase->getNodeType() == NODE_TYPE_MAC && row == 1)//装置NodeMac下挂装置BB
{
CNodeMac *pNodeMac = static_cast<CNodeMac*>(parent.internalPointer());
CNodeBB *pNodeDeviceGroupFolder = pNodeMac->getChildDeviceBB();
return createIndex(row,column,pNodeDeviceGroupFolder);
}
else if (pNodeBase->getNodeType() == NODE_TYPE_MAC && row == 2)//装置NodeMac下挂装置CC
{
CNodeMac *pNodeMac = static_cast<CNodeMac*>(parent.internalPointer());
CNodeCC *pNodeDeviceGroupFolder = pNodeMac->getChildDeviceCC();
return createIndex(row,column,pNodeDeviceGroupFolder);
}
else if (pNodeBase->getNodeType() == NODE_TYPE_AA)//装置AA下挂装置AA-CHILD
{
CNodeAA *pNodeDeviceFolder = static_cast<CNodeAA*>(parent.internalPointer());
CNodeAAChildA *pNodeBase = pNodeDeviceFolder->getChildDevice(row);
return createIndex(row,column,pNodeBase);
}
else if (pNodeBase->getNodeType() == NODE_TYPE_AA_CHILD)//装置AA-CHILD下挂装置
{
CNodeAAChildA *pNodeDeviceFolder = static_cast<CNodeAAChildA*>(parent.internalPointer());
CNodeBase *pNodeBase = pNodeDeviceFolder->getChildDevice(row);
return createIndex(row,column,pNodeBase);
}
else if (pNodeBase->getNodeType() == NODE_TYPE_BB)//装置BB下挂装置BB-CHILD
{
CNodeBB *pNodeDeviceGroupFolder = static_cast<CNodeBB*>(parent.internalPointer());
CNodeBase *pNodeBase = pNodeDeviceGroupFolder->getChildDeviceGroup(row);
return createIndex(row,column,pNodeBase);
}
else if (pNodeBase->getNodeType() == NODE_TYPE_CC)//装置CC下挂装置CC-CHILD
{
CNodeCC *pNodeDeviceGroupFolder = static_cast<CNodeCC*>(parent.internalPointer());
CNodeCCChild *pNodeBase = pNodeDeviceGroupFolder->getChildDevice(row);
return createIndex(row,column,pNodeBase);
}
else if (pNodeBase->getNodeType() == NODE_TYPE_CC_CHILD)//装置CC-CHILD下挂装置CC-CHILD-CHILD
{
CNodeCCChild *pNodeDeviceGroupFolder = static_cast<CNodeCCChild*>(parent.internalPointer());
CNodeCCChildChild *pNodeBase = pNodeDeviceGroupFolder->getChildDevice(row);
return createIndex(row,column,pNodeBase);
}
else if (pNodeBase->getNodeType() == NODE_TYPE_CC_CHILD_CHILD)//装置CC-CHILD-CHILD下挂装置
{
CNodeCCChildChild *pNodeDeviceGroupFolder = static_cast<CNodeCCChildChild*>(parent.internalPointer());
CNodeBase *pNodeBase = pNodeDeviceGroupFolder->getChildDevice(row);
return createIndex(row,column,pNodeBase);
}
return QModelIndex();
}
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
{
return QModelIndex();
}
CNodeBase *childItem = static_cast<CNodeBase*>(index.internalPointer());
CNodeBase *parentItem = childItem->getParent();
if (parentItem->getNodeType() == NODE_TYPE_MAC_MANAGER)//设备管理
{
return QModelIndex();
}
else if(parentItem->getNodeType() == NODE_TYPE_MAC)//设备
{
return createIndex(0, 0, parentItem);
}
else if(parentItem->getNodeType() == NODE_TYPE_AA)//设备下挂装置AA
{
return createIndex(0, 0, parentItem);
}
else if(parentItem->getNodeType() == NODE_TYPE_AA_CHILD)//装置AA下挂装置
{
return createIndex(0, 0, parentItem);
}
else if(parentItem->getNodeType() == NODE_TYPE_BB)//设备下挂装置BB
{
return createIndex(1, 0, parentItem);
}
else if(parentItem->getNodeType() == NODE_TYPE_CC)//设备下挂装置CC
{
return createIndex(2, 0, parentItem);
}
else if(parentItem->getNodeType() == NODE_TYPE_CC_CHILD)//装置CC下挂装置CC-CHILD
{
return createIndex(0, 0, parentItem);
}
else if(parentItem->getNodeType() == NODE_TYPE_CC_CHILD_CHILD)//装置CC-CHILD-CHILD下挂装置
{
return createIndex(0, 0, parentItem);
}
return QModelIndex();
}
int TreeModel::rowCount(const QModelIndex &parent) const
{
CNodeBase *parentItem;
if (parent.column() > 0)
{
return 0;
}
if (!parent.isValid())
{
return m_pDevRoot->getMacCount();
}
parentItem = static_cast<CNodeBase*>(parent.internalPointer());
if(parentItem->getNodeType() == NODE_TYPE_MAC)//设备下挂数量
{
CNodeMac *p = static_cast<CNodeMac*>(parent.internalPointer());
return p->getChildCount();
}
else if(parentItem->getNodeType() == NODE_TYPE_AA)//装置AA下挂数量
{
CNodeAA *p = static_cast<CNodeAA*>(parent.internalPointer());
return p->getChildCount();
}
else if(parentItem->getNodeType() == NODE_TYPE_AA_CHILD)//装置AA下挂装置AA-CHILD下挂数量
{
CNodeAAChildA *p = static_cast<CNodeAAChildA*>(parent.internalPointer());
return p->getChildCount();
}
else if(parentItem->getNodeType() == NODE_TYPE_BB)//装置BB下挂数量
{
CNodeBB *p = static_cast<CNodeBB*>(parent.internalPointer());
return p->getChildCount();
}
else if(parentItem->getNodeType() == NODE_TYPE_CC)//装置CC下挂数量
{
CNodeCC *p = static_cast<CNodeCC*>(parent.internalPointer());
return p->getChildCount();
}
else if(parentItem->getNodeType() == NODE_TYPE_CC_CHILD)//装置CC-CHILD下挂数量
{
CNodeCCChild *p = static_cast<CNodeCCChild*>(parent.internalPointer());
return p->getChildCount();
}
else if(parentItem->getNodeType() == NODE_TYPE_CC_CHILD_CHILD)//装置CC-CHILD-CHILD下挂数量
{
CNodeCCChildChild *p = static_cast<CNodeCCChildChild*>(parent.internalPointer());
return p->getChildCount();
}
return 0;
}
int TreeModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
void TreeModel::setupModelData()
{
CNodeMac *p = new CNodeMac(m_pDevRoot);
m_pDevRoot->appendChlid(p);
}
类似这种通知在手机上也比较常见,一般就是软件有新版本这种类型的通知比较多了。这种通知其实是对Intent的封装,这种意图不会立刻执行。还需要注意的是封装出来的这个东西和Intent并没有继承关系,完成这种操作需要Notification组件来完成,而这样做就需要取得系统服务之后配合PendingIntent来完成。来看下代码吧,比较简单了大家随便看看。
package org.lxh.myactivity; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.os.Bundle; public class PendingActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //取得操作系统进程 NotificationManager notificationManager=(NotificationManager)super.getSystemService(Activity.NOTIFICATION_SERVICE); Notification notification=new Notification(R.drawable.pic_m, "来自软件更新的消息", System.currentTimeMillis()); PendingIntent pIntent=PendingIntent.getActivity(this, 0, super.getIntent(), PendingIntent.FLAG_UPDATE_CURRENT); notification.setLatestEventInfo(this, "你的软件有新版本", "软件有新版本请及时更新", pIntent); notificationManager.notify("softupdate", R.drawable.pic_m, notification); } }
我就直接上效果图了
下面还有一个
很简单吧。