最近在做一个关于微信公众平台服务号的小项目,主要用来实现排队叫号功能。一直都对微信公众号开发比较好奇,于是趁这次机会仔细研究了一下公众号的开发流程和逻辑架构。
微信公众平台现在分为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个。