最近在做一个关于微信公众平台服务号的小项目,主要用来实现排队叫号功能。一直都对微信公众号开发比较好奇,于是趁这次机会仔细研究了一下公众号的开发流程和逻辑架构。
微信公众平台现在分为3类:订阅号,服务号和企业号。其中,服务号和企业号的开放权限比较高,可以实现自定义菜单功能,调用摄像头以及LBS等API。
基本通信架构如图:
在项目的功能设计阶段本想搭建一个服务号Demo用来展示,但微信服务号的认证手续太麻烦,而且我也没有那个资质去开通服务号。于是打算自己做一个仿微信公众号的基本界面,先实现菜单功能,避免开发初期的公众号注册,同时也方便展示。
先上效果图:
1. 界面布局
主界面布局四部分,由上到下依次是:标题栏,消息列表,底部菜弹出的子菜单,底部菜单或输入栏。
主界面基本框架main.xml代码如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#E4E4E4"
<!-- 消息列表 --
<ListView
android:id="@+id/lv"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginBottom="50dp"
android:layout_marginTop="10dp"
android:cacheColorHint="#00000000"
android:divider="#00000000"
android:dividerHeight="20dp"
android:scrollbars="none"
</ListView
<!-- 点击底部菜单后弹出的子菜单 --
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="50dp"
android:orientation="horizontal"
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.5" /
<LinearLayout
android:id="@+id/pop_layout1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_weight="1"
android:orientation="vertical"
</LinearLayout
<LinearLayout
android:id="@+id/pop_layout2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_weight="1"
android:orientation="vertical"
</LinearLayout
<LinearLayout
android:id="@+id/pop_layout3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_weight="1"
android:orientation="vertical"
</LinearLayout
</LinearLayout
<!-- 底部菜单 --
<LinearLayout
android:id="@+id/bottom_layout"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:background="#00ffffff"
android:orientation="vertical"
<LinearLayout
android:id="@+id/bottom_menu_layout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffff"
android:orientation="vertical"
<View
android:layout_width="fill_parent"
android:layout_height="1px"
android:background="#A6A6A6" /
<LinearLayout
android:id="@+id/menu_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="horizontal"
<ImageView
android:id="@+id/keyboard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:layout_marginTop="5dp"
android:layout_weight="0.5"
android:background="@drawable/keyboard" /
<View
android:layout_width="1px"
android:layout_height="fill_parent"
android:background="#A6A6A6" /
<RelativeLayout
android:id="@+id/btn1"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:background="@drawable/btn_selector"
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="5dp"
<TextView
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="用户绑定"
android:textColor="#000000"
android:textSize="16sp" /
<ImageView
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:src="@drawable/more_icon"
android:visibility="invisible" /
</RelativeLayout
</RelativeLayout
<View
android:layout_width="1px"
android:layout_height="fill_parent"
android:background="#A6A6A6" /
<RelativeLayout
android:id="@+id/btn2"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:background="@drawable/btn_selector"
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="5dp"
<TextView
android:id="@+id/text2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="扫描签到"
android:textColor="#000000"
android:textSize="16sp" /
<ImageView
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:src="@drawable/more_icon"
android:visibility="invisible" /
</RelativeLayout
</RelativeLayout
<View
android:layout_width="1px"
android:layout_height="fill_parent"
android:background="#A6A6A6" /
<RelativeLayout
android:id="@+id/btn3"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:background="@drawable/btn_selector"
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="5dp"
<TextView
android:id="@+id/text3"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:text="更多"
android:textColor="#000000"
android:textSize="16sp" /
<ImageView
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:src="@drawable/more_icon"
android:visibility="visible" /
</RelativeLayout
</RelativeLayout
</LinearLayout
</LinearLayout
</LinearLayout
</FrameLayout
标题栏title_bar.xml布局如下:
<?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:orientation="horizontal"
<!-- 返回 --
<ImageView
android:id="@+id/title_bar_back_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="10dip"
android:src="@drawable/back" /
<!-- 服务号名称 --
<TextView
android:id="@+id/my_setting_title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="腾讯招聘面试服务"
android:textColor="#ffffff"
android:textSize="20sp" /
<!-- 服务号 --
<ImageView
android:id="@+id/title_bar_my"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dip"
android:src="@drawable/my" /
</RelativeLayout
完成title_bar布局后,再在values\styles.xml添加自定义标题栏主题
<!-- 自定义标题栏背景颜色 --
<style name="CustomWindowTitleBackground"
<item name="android:background" #32394A</item
</style
<!-- 自定义标题栏主题 --
<style name="myTheme" parent="android:Theme"
<item name="android:windowTitleSize" 45dp</item
<item name="android:windowTitleBackgroundStyle" @style/CustomWindowTitleBackground</item
</style
消息列表的服务端消息item布局item_left.xml
<?xml version="1.0" encoding="utf-8"?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
<RelativeLayout
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="4"
<ImageView
android:id="@+id/server_image"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="2dp"
android:background="@drawable/qq"/
<TextView
android:id="@+id/server_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="2dp"
android:layout_toRightOf="@id/server_image"
android:background="@drawable/text_bg_left1"
android:gravity="center_vertical|left"
android:textSize="16sp"
android:textColor="#000000"/
</RelativeLayout
<View
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" /
</LinearLayout
消息列表的用户消息item布局item_right.xml
<?xml version="1.0" encoding="utf-8"?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
<View
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" /
<RelativeLayout
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="4"
<ImageView
android:id="@+id/user_image"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:layout_marginRight="2dp"
android:background="@drawable/qq" /
<TextView
android:id="@+id/user_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="1dp"
android:layout_toLeftOf="@id/user_image"
android:background="@drawable/text_bg_right1"
android:gravity="center_vertical|right"
android:textColor="#000000"
android:textSize="16sp" /
</RelativeLayout
</LinearLayout
弹出的子菜单布局child_menu.xml如下:
<?xml version="1.0" encoding="utf-8"?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/child_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FFFFFF"
android:gravity="bottom"
android:orientation="vertical"
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<Button
android:id="@+id/test1"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:background="@drawable/btn_selector"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="进度查询"
android:textColor="#000000"
android:textSize="16sp" /
<View
android:layout_width="wrap_content"
android:layout_height="1px"
android:layout_alignLeft="@id/test1"
android:layout_alignRight="@id/test1"
android:layout_below="@id/test1"
android:background="#E4E4E4" /
</RelativeLayout
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<Button
android:id="@+id/test1"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:background="@drawable/btn_selector"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="使用帮助"
android:textColor="#000000"
android:textSize="16sp" /
<View
android:layout_width="wrap_content"
android:layout_height="1px"
android:layout_alignLeft="@id/test1"
android:layout_alignRight="@id/test1"
android:layout_below="@id/test1"
android:background="#E4E4E4" /
</RelativeLayout
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<Button
android:id="@+id/test1"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:background="@drawable/btn_selector"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="联系我们"
android:textColor="#000000"
android:textSize="16sp" /
<View
android:layout_width="wrap_content"
android:layout_height="1px"
android:layout_alignLeft="@id/test1"
android:layout_alignRight="@id/test1"
android:layout_below="@id/test1"
android:background="#E4E4E4" /
</RelativeLayout
</LinearLayout
由底部菜单切换到输入框,输入框bottom_menu_layout2.xml布局如下:
<?xml version="1.0" encoding="utf-8"?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
<LinearLayout
android:id="@+id/bottom_menu_layout2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffff"
android:orientation="vertical"
<View
android:layout_width="fill_parent"
android:layout_height="1px"
android:background="#A6A6A6" /
<LinearLayout
android:id="@+id/menu_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="horizontal"
<!-- 左侧切换菜单按钮 --
<ImageView
android:id="@+id/menu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:layout_marginTop="5dp"
android:layout_weight="0.5"
android:background="@drawable/menu" /
<View
android:layout_width="1px"
android:layout_height="fill_parent"
android:background="#A6A6A6" /
<RelativeLayout
android:id="@+id/btn1"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_margin="5dp"
android:layout_weight="3"
android:background="#ffffff"
<ImageView
android:id="@+id/voice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:src="@drawable/voice" /
<!-- 右侧“+”按钮或发送按钮 --
<Button
android:id="@+id/add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:layout_marginRight="1dp"
android:background="@drawable/add"
android:paddingBottom="5dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="5dp"
android:text=""
android:textColor="#ffffff"
android:textSize="14sp" /
<!-- 输入 --
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@id/add"
android:layout_toRightOf="@id/voice"
<EditText
android:id="@+id/input_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:background="#00000000"
android:gravity="bottom"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:text=""
android:textColor="#000000"
android:textSize="16sp" /
<View
android:layout_width="fill_parent"
android:layout_height="1px"
android:layout_below="@id/input_text"
android:layout_marginTop="10dp"
android:background="#A6A6A6" /
</RelativeLayout
</RelativeLayout
</LinearLayout
</LinearLayout
</LinearLayout
2. 代码实现
MainActivity.java
package com.example.wxdemo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class MainActivity extends Activity implements View.OnClickListener {
private LinearLayout bottomLayout;// 底部菜单父框架
private LinearLayout bottomMenuLayout1;// 底部菜单布局
private LinearLayout bottomMenuLayout2;// 底部输入框布局
private RelativeLayout btn1;// “用户绑定”按钮布局
private RelativeLayout btn2;// “扫描签到”按钮布局
private RelativeLayout btn3;// “更多”按钮布局
private LinearLayout popLayout1;
private LinearLayout popLayout2;
private LinearLayout popLayout3;// 弹出的子菜单父框架布局
private LinearLayout childLayout;// “更多”按钮的子菜单
private ListView lv;
private MyAdapter adapter;
private List<Map<String, String listData = new ArrayList<Map<String, String ();
private ImageView keyboard;// 底部键盘切换图标
private ImageView menu;// 底部菜单切换图标
private Button send;// 发送按钮
private EditText inputText;// 输入框
private boolean open = true;// 子菜单填充状态标记
private boolean flag = false;// 子菜单显示状态标记
private boolean bind = false;// 用户绑定状态标记
private Animation animEnter;// 底部菜单进入动画
private Animation animExit;// 底部菜单退出动画
private View view;
private View view2;
private LayoutInflater inflater;
private int myID = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.main);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,
R.layout.title_bar);// 自定义标题栏
inflater = MainActivity.this.getLayoutInflater();
popLayout1 = (LinearLayout) findViewById(R.id.pop_layout1);
popLayout2 = (LinearLayout) findViewById(R.id.pop_layout2);
popLayout3 = (LinearLayout) findViewById(R.id.pop_layout3);
bottomLayout = (LinearLayout) findViewById(R.id.bottom_layout);
bottomMenuLayout1 = (LinearLayout) findViewById(R.id.bottom_menu_layout1);
keyboard = (ImageView) findViewById(R.id.keyboard);
btn1 = (RelativeLayout) findViewById(R.id.btn1);
btn2 = (RelativeLayout) findViewById(R.id.btn2);
btn3 = (RelativeLayout) findViewById(R.id.btn3);
lv = (ListView) findViewById(R.id.lv);
btn1.setOnClickListener(this);
btn2.setOnClickListener(this);
btn3.setOnClickListener(this);
keyboard.setOnClickListener(this);
adapter = new MyAdapter(this, listData);
lv.setAdapter(adapter);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
int id = v.getId();
switch (id) {
case R.id.btn1:
btn1Click();
break;
case R.id.btn2:
break;
case R.id.btn3:
btn3Click();
break;
case R.id.keyboard:
keyboardClick();
break;
case R.id.menu:
menuClick();
break;
case R.id.add:
sendClick();
break;
default:
break;
}
}
public void btn1Click() {// 用户绑定
Map<String, String map = new HashMap<String, String ();
map.put("type", "0");
if (!bind) {
map.put("text", "请输入您的手机号或简历ID进行帐号绑定,绑定成功后才能进行签到。");
} else {
map.put("text", "帐号已绑定成功,请您准时签到。");
}
listData.add(map);
adapter.notifyDataSetChanged();
}
public void btn2Click() {// 扫描签到
// TODO
}
public void btn3Click() {// 更多
if (open == true) {
view = inflater.inflate(R.layout.child_menu, popLayout3, true);
childLayout = (LinearLayout) view.findViewById(R.id.child_layout);
open = false;
}
if (flag == false) {
flag = true;
childLayout.setVisibility(View.VISIBLE);
} else {
flag = false;
childLayout.setVisibility(View.GONE);
}
}
public void keyboardClick() {//点击键盘按钮,由底部菜单切换为底部输入
view2 = inflater.inflate(R.layout.bottom_menu_layout2, bottomLayout,
true);
bottomMenuLayout2 = (LinearLayout) view2
.findViewById(R.id.bottom_menu_layout2);
animEnter = AnimationUtils.loadAnimation(MainActivity.this,
R.anim.my_pop_enter_anim);
animExit = AnimationUtils.loadAnimation(MainActivity.this,
R.anim.my_pop_exit_anim);
animEnter.setStartOffset(200);
bottomMenuLayout1.startAnimation(animExit);
bottomMenuLayout1.setVisibility(View.GONE);
bottomMenuLayout2.startAnimation(animEnter);
bottomMenuLayout2.setVisibility(View.VISIBLE);
menu = (ImageView) view2.findViewById(R.id.menu);
inputText = (EditText) view2.findViewById(R.id.input_text);
send = (Button) view2.findViewById(R.id.add);
menu.setOnClickListener(this);
send.setOnClickListener(this);
inputClick();
}
public void menuClick() {//点击菜单按钮,由底部输入框切换为底部菜单
bottomMenuLayout2.startAnimation(animExit);
bottomMenuLayout2.setVisibility(View.GONE);
bottomMenuLayout1.startAnimation(animEnter);
bottomMenuLayout1.setVisibility(View.VISIBLE);
}
public void inputClick() {
inputText.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
// TODO Auto-generated method stub
if (hasFocus) {
send.setBackgroundResource(R.drawable.send_btn_bg);
send.setText("发送");
} else {
send.setBackgroundResource(R.drawable.add);
send.setText(" ");
}
}
});
}
public void sendClick() {
String text = inputText.getEditableText().toString();
inputText.setText("");
if (text != null && (!text.equals(""))) {
Map<String, String map;
map = new HashMap<String, String ();
map.put("type", "1");// 消息类型,服务端为0,用户为1
map.put("text", text);
listData.add(map);
map = new HashMap<String, String ();
map.put("type", "0");
map.put("text", "帐号已绑定成功,请您准时签到。");
listData.add(map);
adapter.notifyDataSetChanged();
bind = true;
}
}
private class MyAdapter extends BaseAdapter {
public List<Map<String, String list;
private Context context;
private int type;
private ListView listView;
public MyAdapter(Context context, List<Map<String, String list) {
this.context = context;
this.list = list;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder viewHolder = null;
Map<String, String map = (Map<String, String ) list.get(position);
if (map.get("type").equals("0")) {// 服务端
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_left, parent,
false);
viewHolder = new ViewHolder();
viewHolder.mTextView = (TextView) convertView
.findViewById(R.id.server_text);
viewHolder.mImageView = (ImageView) convertView
.findViewById(R.id.server_image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.mTextView.setText(map.get("text"));
} else {// 用户
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_right, parent,
false);
viewHolder = new ViewHolder();
viewHolder.mTextView = (TextView) convertView
.findViewById(R.id.user_text);
viewHolder.mImageView = (ImageView) convertView
.findViewById(R.id.user_image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.mTextView.setText(map.get("text"));
}
return convertView;
}
}
private final class ViewHolder {
TextView mTextView;
ImageView mImageView;
}
}
以上就是实现仿微信服务号的主要代码,菜单功能并没用完全实现,可根据实际情况和需要进行添加。同时还需注意的是,底部菜单最多为3个,每个名称限制在7个字符,包含的子菜单最多只能有5个。