本篇主要介绍了一下本人正式完成的第一个Android demo应用。

1、demo运行效果图

2、为了实现微信的体验效果,这里面用到了几个特殊的控件或者技术。

2.1 第一张图中的“+”图片点击弹出2级菜单,这个地方使用了自定义的ActionProvider,具体实现代码如下。

"+"号菜单是titlebar上的menu菜单,通过menu文件配置

<menu xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"tools:context="com.example.walletdemo.MainActivity" ><itemandroid:id="@+id/action_search"android:icon="@drawable/actionbar_search_icon1"android:showAsAction="ifRoom"android:title="@string/action_search"/><item android:id="@+id/action_plus"android:actionProviderClass="com.example.wechatdemo.ui.widget.PlusActionProvider"android:icon="@drawable/actionbar_add_icon1"android:showAsAction="always"android:title="@string/action_plus"/></menu>

自定义"+"号子菜单的实现

/*** * <actionbar子菜单>* <功能详细描述>* */
public class PlusActionProvider extends ActionProvider
{//启动二维码扫描请求码private static final int REQUEST_FIND = 1;private Context context;public PlusActionProvider(final Context context){super(context);this.context = ((ContextWrapper)context).getBaseContext();}@Overridepublic View onCreateActionView(){return null;}@Overridepublic void onPrepareSubMenu(SubMenu subMenu){subMenu.clear();subMenu.add(context.getString(R.string.sub_plus_group_chat)).setIcon(R.drawable.sub_plus_group_chat_icon).setOnMenuItemClickListener(new OnMenuItemClickListener(){@Overridepublic boolean onMenuItemClick(MenuItem item){return true;}});subMenu.add(context.getString(R.string.sub_add_friend)).setIcon(R.drawable.sub_add_friend_icon).setOnMenuItemClickListener(new OnMenuItemClickListener(){@Overridepublic boolean onMenuItemClick(MenuItem item){final EditText inputServer = new EditText(context);AlertDialog.Builder builder = new AlertDialog.Builder(context);builder.setTitle("请输入用户名").setIcon(android.R.drawable.ic_dialog_info).setView(inputServer).setNegativeButton("Cancel", null);builder.setPositiveButton("Ok", new OnClickListener(){public void onClick(DialogInterface dialog, int which){final String friendUserName = inputServer.getText().toString();SharedPreferences sharedPreferences =context.getSharedPreferences("user", Context.MODE_PRIVATE); //私有数据final String userId = sharedPreferences.getString("userId", "");UserComponent.addFriend(context, friendUserName, userId);}});builder.show();return true;}});subMenu.add(context.getString(R.string.sub_scan)).setIcon(R.drawable.sub_scan_icon).setOnMenuItemClickListener(new OnMenuItemClickListener(){@Overridepublic boolean onMenuItemClick(MenuItem item){Intent intent = new Intent(context, CaptureActivity.class);((Activity)context).startActivityForResult(intent, REQUEST_FIND);return true;}});subMenu.add(context.getString(R.string.sub_take_money)).setIcon(R.drawable.sub_take_money_icon).setOnMenuItemClickListener(new OnMenuItemClickListener(){@Overridepublic boolean onMenuItemClick(MenuItem item){return true;}});subMenu.add(context.getString(R.string.sub_help_feedback)).setIcon(R.drawable.sub_help_feedback_icon).setOnMenuItemClickListener(new OnMenuItemClickListener(){@Overridepublic boolean onMenuItemClick(MenuItem item){return true;}});}@Overridepublic boolean hasSubMenu(){return true;}}

2.2 第二张图中,右侧有一个快速定位索引控件,这个控件的实现如下:

该fragment的布局,其中使用了一个自定义的MyLetterListView

<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><ListView android:id="@+id/contacts_listview"android:layout_width="match_parent"android:layout_height="match_parent"></ListView><com.example.wechatdemo.ui.widget.MyLetterListView android:id="@+id/letter_listview"android:layout_width="30dp"android:layout_height="match_parent"android:layout_alignParentRight="true"/>
</RelativeLayout></span>

自定义的MyLetterListView的具体实现

<span style="font-size:18px;">public class MyLetterListView extends View
{/*** 申明字母监听对象*/private OnTouchingLetterChangedListener onTouchingLetterChangedListener;/*** 定义字母数组*/private String[] letter = {"@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P","Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"};/*** 记录当前选中的字母*/int choose = -1;private Paint paint = new Paint();boolean showBkg = false;public MyLetterListView(Context context, AttributeSet attrs, int defStyleAttr){super(context, attrs, defStyleAttr);}public MyLetterListView(Context context, AttributeSet attrs){super(context, attrs);}public MyLetterListView(Context context){super(context);}/*** 绘制右侧字母表*/@Overrideprotected void onDraw(Canvas canvas){super.onDraw(canvas);if (showBkg){canvas.drawColor(Color.parseColor("#40000000"));}int height = getHeight();int width = getWidth();int singleHeight = height / letter.length;for (int i = 0; i < letter.length; i++){paint.setTextSize(18f);paint.setColor(Color.BLACK);paint.setTypeface(Typeface.DEFAULT_BOLD);paint.setAntiAlias(true);if (i == choose){paint.setColor(Color.parseColor("#3399ff"));paint.setFakeBoldText(true);}float xPos = width / 2 - paint.measureText(letter[i]) / 2;float yPos = singleHeight * i + singleHeight;canvas.drawText(letter[i], xPos, yPos, paint);paint.reset();}}/*** 分发触摸事件*/@Overridepublic boolean dispatchTouchEvent(MotionEvent event){int action = event.getAction();float y = event.getY();float x = event.getX();int oldChoose = choose;OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;int c = (int)(y / getHeight() * letter.length);switch (action){case MotionEvent.ACTION_DOWN:showBkg = true;if (oldChoose != c && listener != null){if (c > 0 && c < letter.length){listener.onTouchingLetterChanged(letter[c], y, x);choose = c;invalidate();}}break;case MotionEvent.ACTION_MOVE:if (oldChoose != c && listener != null){if (c > 0 && c < letter.length){listener.onTouchingLetterChanged(letter[c], y, x);choose = c;invalidate();}}break;case MotionEvent.ACTION_UP:showBkg = false;choose = -1;listener.onTouchingLetterEnd();invalidate();break;default:break;}return true;}public void setOnTouchinfLetterChangedListener(OnTouchingLetterChangedListener onTouchingLetterChangedListener){this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;}/*** * <定义字母监听状态接口>* <功能详细描述>* * @author  x00355498* @version  [版本号, 2015年8月13日]* @see  [相关类/方法]* @since  [产品/模块版本]*/public interface OnTouchingLetterChangedListener{public void onTouchingLetterEnd();public void onTouchingLetterChanged(String s, float y, float x);}
}
</span>

2.3 第三张图和第四张图主要是实现微信的布局效果,没有使用特殊的控件或自定义布局,这里就不贴布局代码了。

2.4 前四张图构成了mainactivity,从布局上看mainactivity,其实就是一个Viewpager+一个RadioGroup组成,RadioGroup中包含了4个RadioButton。在mainactivity中使用了一个ViewPager容器来管理4个fragment,而没有直接使用FragmentManager管理这4个fragment,从而显示了类似微信的左右滑动切换fragment的效果。需要主要一点的是,左右滑动触发ViewPager事件时,需要再里面触发RadioButton的点击事件,已达到下方按钮的状态同步更改。

<span style="font-size:18px;">public class ViewPagerAdapter extends FragmentPagerAdapter
{private Fragment[] fragment;public ViewPagerAdapter(FragmentManager fragmentManager,Fragment[] fragment){super(fragmentManager);this.fragment = fragment;}@Overridepublic Fragment getItem(int arg0){return fragment[arg0];}@Overridepublic int getCount(){return fragment.length;}}</span>
<span style="font-size:18px;">@SuppressWarnings("deprecation")private void initViewPager(){mViewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(),fragment);  mViewPager = (ViewPager)findViewById(R.id.pager);  mViewPager.setAdapter(mViewPagerAdapter);  mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {  @Override  public void onPageSelected(int position) {  radioButton[position].performClick();}  @Override  public void onPageScrollStateChanged(int state) {  switch(state) {  case ViewPager.SCROLL_STATE_IDLE:  ((FriendsFragment)fragment[1]).showLetterListView();break;  case ViewPager.SCROLL_STATE_DRAGGING:  ((FriendsFragment)fragment[1]).hiddenLetterListView();break;  case ViewPager.SCROLL_STATE_SETTLING:  break;  default:  break;  }  }  });  }</span>
/*** <设置radiobutton按钮图片和文字颜色>* <功能详细描述>* @param index* @param temp* @param colorId* @see [类、类#方法、类#成员]*/private void setRadioButtonDrawableTop(int index,int drawableId,int colorId){Drawable temp = getResources().getDrawable(drawableId);temp.setBounds(0, 0, temp.getMinimumWidth(), temp.getMinimumHeight());radioButton[index].setCompoundDrawables(null, temp, null, null);radioButton[index].setTextColor(getResources().getColor(colorId));}@Overridepublic void onCheckedChanged(RadioGroup group, int checkedId){initRadioGroup();switch (checkedId){case R.id.btn_chat:setRadioButtonDrawableTop(0,R.drawable.msg_on_icon,R.color.blue);mViewPager.setCurrentItem(0);break;case R.id.btn_contacts:setRadioButtonDrawableTop(1,R.drawable.contacts_on_icon,R.color.blue);mViewPager.setCurrentItem(1);break;case R.id.btn_find:setRadioButtonDrawableTop(2,R.drawable.find_on_icon,R.color.blue);mViewPager.setCurrentItem(2);break;case R.id.btn_my:setRadioButtonDrawableTop(3,R.drawable.my_on_icon,R.color.blue);mViewPager.setCurrentItem(3);break;}}

2.5 集成融云SDK,实现即时通讯能力。由于即时通讯需要自己再服务器搭建一个Openfire,由于没有自己的个人外网服务器,所以就选择使用了融云SDK来替代这部分的功能。融云SDK提供了即时通讯的能力,但是为了第三方接入的保密性,它并没有提供用户能力,对于每一个回话的创建,只需要传入一个用户标识id即可,用户登陆也类似,用用户的标识id去换取一个token,使用该token就可以连接服务器了。这里就不详细介绍这个部门了,感兴趣的话,大家可以去融云官网看看。 融云官网

2.6 搭建了自己的用户中心,既然融云SDK没有提供用户能力,那我们想实现类似微信的即时通讯,就还需要一套自己的用户中心服务器。在这里我使用的是新浪的SAE平台搭建的用户中心服务器,这个服务器搭建很简单,提供了一个基础接口,也没有加入任何的接入权限控制,毕竟这里我是为了学习android的知识内容。具体服务器代码如下(php代码):

test.php

<span style="font-size:18px;"><?php header('Content-type: application/json');include('api.php');$action = isset($_REQUEST["action"])?$_REQUEST["action"]:null;switch($action){case 'login':$username=isset($_REQUEST["username"])?$_REQUEST["username"]:null;$password=isset($_REQUEST["password"])?$_REQUEST["password"]:null;echo login($username,$password);break;case 'register':$username=isset($_REQUEST["username"])?$_REQUEST["username"]:null;$password=isset($_REQUEST["password"])?$_REQUEST["password"]:null;$headurl=isset($_REQUEST["headurl"])?$_REQUEST["headurl"]:null;echo register($username,$password,$headurl);break;case 'userInfo':$uid=isset($_REQUEST["uid"])?$_REQUEST["uid"]:null;$sessionId=isset($_REQUEST["sessionId"])?$_REQUEST["sessionId"]:null;echo userInfo($uid,$sessionId,$deviceId);break;case 'getUserId':$username=isset($_REQUEST["username"])?$_REQUEST["username"]:null;echo getUserId($username);break;case 'addFriend':$userId=isset($_REQUEST["userId"])?$_REQUEST["userId"]:null;$friendId=isset($_REQUEST["friendId"])?$_REQUEST["friendId"]:null;echo addFriend($userId,$friendId);break;case 'getFriends':$userId=isset($_REQUEST["userId"])?$_REQUEST["userId"]:null;echo getFriends($userId);break;default:break;}</span>

api.php

<span style="font-size:18px;"><?phprequire('rongyun.php');
require('db/db.php');/***用户注册*/
function register($username,$password,$headurl){$res = user_register($username,$password,$headurl);$strjson = json_encode($res);return $strjson;
}/***用户登录*/
function login($username,$password){$res = user_login($username,$password);$userId = $res["userId"];$token = getToken($userId);$res['token']=$token;$strjson = json_encode($res);return $strjson;
}/***用户信息*/
function userInfo($uid,$sessionId,$deviceId){$timestamp = date("Y-m-d H:i:s",time());$data=array("app_key"=>appkey,"timestamp"=>$timestamp,"tooken"=>token,"signType"=>signType,"version"=>version,"deviceId"=>$deviceId,"appOs"=>appOs,);$sign = markSignStr($data,appSecret,"app_key","timestamp");$data["sign"]=$sign;$data["action"]="userinfo";$data["uid"]=$uid;$data["sessionId"]=$sessionId;$res = http_request(host."/api/user.j",$data);return $res;
}/**
*通过用户名查询用户Id
*/
function getUserId($username){$res = user_get_userId($username);$strjson = json_encode($res);return $strjson;
}/**
*增加好友关系
*/
function addFriend($userId,$friendId){$res = user_add_friend($userId,$friendId);$strjson = json_encode($res);return $strjson;
}/**
*获取好友列表
*/
function getFriends($userId){$res = user_get_friends($userId);$strjson = json_encode($res);return $strjson;
}
</span>

rongyun.php

<span style="font-size:18px;"><?php//require('tools/SignUtils.php');require('tools/HttpUtils.php');//开发者平台分配的AppSecretdefine("appkey","x18ywvqf8urbc");define("appsecret","g17YM77uV2");define("host","https://api.cn.rong.io");function sign(){// 重置随机数种子。srand((double)microtime()*1000000);$nonce = rand(); // 获取随机数。$timestamp = time(); // 获取时间戳。$signature = sha1(appsecret.$nonce.$timestamp);$data=array('RC-App-Key: '.appkey,'RC-Nonce: '.$nonce,'RC-Timestamp: '.$timestamp,'RC-Signature: '.$signature);return $data;}function getToken($userId,$name = null,$headurl = null){$header = sign();$data = Array("userId"=>$userId,"name"=>$name,"portraitUri"=>$headurl);$res = http_request(host."/user/getToken.json",$data,$header);$res = json_decode($res,true);if($res['code']==200){return $res['token'];}else{return null;}}</span>

db.php

<span style="font-size:18px;"><?phpdefine("mysql_server_name","数据库服务器地址");define("mysql_database","数据库名");define("mysql_username","用户名");define("mysql_password","密码");/***执行实际的数据库查询操作*/function db_query($strsql){// 连接到数据库$conn=mysql_connect(mysql_server_name, mysql_username,mysql_password)or die("Unable to connect to the MySQL!");//设置字符集mysql_query("set names 'utf8'");mysql_select_db(mysql_database, $conn);          //选择数据库  // 执行sql查询$result=mysql_query($strsql, $conn);return $result;mysql_free_result($result);mysql_close($conn);}/***用户注册*/function user_register($username,$password,$headurl){$data = array("code"=>"-1","msg"=>"error");// 查询用户名是否存在$strsql="SELECT * FROM users WHERE username = '$username'";// 执行sql查询$result=db_query($strsql);if(!mysql_fetch_array($result)){//插入新用户$strsql="INSERT INTO users(username,password,headurl) VALUES('$username','$password','$headurl')";// 执行sql查询$result=db_query($strsql);if($result==1){$data = array("code"=>"200","msg"=>"register success");}else{$data = array("code"=>"-1","msg"=>"register error");}}return $data;}/***用户登陆*/function user_login($username,$password){// 查询用户名是否存在$strsql="SELECT * FROM users WHERE username = '$username' and password = '$password'";// 执行sql查询$result=db_query($strsql);$row = mysql_fetch_array($result);if(empty($row)){$data = array("code"=>"-1","msg"=>"error");}else{$data = array("code"=>"200","userId"=>$row[0]);}return $data;}/***通过用户名查询用户Id*/function user_get_userId($username){// 查询用户名是否存在$strsql="SELECT * FROM users WHERE username = '$username'";// 执行sql查询$result=db_query($strsql);$row = mysql_fetch_array($result);if(empty($row)){$data = array("code"=>"-1","msg"=>"error");}else{$data = array("code"=>"200","userId"=>$row[0]);}return $data;}/***添加好友*/function user_add_friend($userId,$friendId){$data = array("code"=>"-2","msg"=>"error");// 查询用户名是否存在$strsql="SELECT * FROM user_friends WHERE userId = '$userId' and friendId = '$friendId'";// 执行sql查询$result=db_query($strsql);if(!mysql_fetch_array($result)){//插入新好友用户$strsql="INSERT INTO user_friends(userId,friendId) VALUES('$userId','$friendId')";// 执行sql查询$result=db_query($strsql);if($result==1){$data = array("code"=>"200","msg"=>"add success");}else{$data = array("code"=>"-1","msg"=>"add error");}}return $data;}function user_get_friends($userId){$data = array("code"=>"-1","msg"=>"error");// 查询用户名是否存在$strsql="SELECT b.username,b.id,b.headurl FROM user_friends a, users b WHERE userId = '$userId' and a.friendId = b.id";// 执行sql查询$result=db_query($strsql);$friends = array();while($row = mysql_fetch_array($result)){array_push($friends,array("username"=>$row[0],"userId"=>$row[1],"headurl"=>$row[2]));}if(!empty($friends)){$data = array("code"=>"200","friends"=>$friends);}else{$data = array("code"=>"-1","msg"=>"get error");}return $data;}</span>

2.7 微信扫一扫界面实现,二维码扫描的实现,我这里依赖了google提供的zxing库,具体zxing库的内容,大家可以去github上去搜索。

2.8 第二个fragment提供了类似通讯录的功能,为了实现添加完用户(即调用完增加用户接口),马上更新界面,这个是用了一个变种的观察者模式。具体该模式的运用,大家可以看我之前写的一个博客变种的观察者模式

总结:目前demo中还没有实现类似朋友圈、摇一摇、附近的人等功能,由于进入公司差不多一个月了,完全自我学习的时间已经没有多少了,准备开始融入部门的工作中了,后面可能没有时间继续完善了。

基于融云SDK实现高仿微信相关推荐

  1. 高仿微信6.5.7(融云版)

    一.简述 本项目由 CSDN_LQR 个人独立开发. 项目博客地址:高仿微信6.5.7(融云版) 项目源码地址: GitHub:https://github.com/GitLqr/LQRWeChat ...

  2. Android 高仿微信实时聊天 基于百度云推送

    一直在仿微信界面,今天终于有幸利用百度云推送仿一仿微信聊天了~~~ 首先特别感谢:weidi1989分享的Android之基于百度云推送IM ,大家可以直接下载:省了很多事哈,本例中也使用了weidi ...

  3. android开发百度地图坐标偏差,利用百度地图Android sdk高仿微信发送位置功能及遇到的问题...

    接触了百度地图开发平台半个月了,这2天试着模仿了微信给好友发送位置功能,对百度地图的操作能力又上了一个台阶 我在实现这个功能的时候,遇到一些困难,可能也是别人将会遇到的困难,特在此列出 1.在微信发送 ...

  4. android+高仿视频录制,高仿微信视频录制, 涂鸦水印添加, 基于 ffmpeg 视频编辑

    功能主要包含5点: 1.基于ffmpeg的视频拍摄及合成; 2.自定义拍摄按钮, 长按放大并且显示拍摄进度; 3.自定义view, 实现手绘涂鸦; 4.自定义可触摸旋转缩放位移的表情文字view; 5 ...

  5. java融云即时通开发流程,基于融云开发的 Android 版即时通讯(IM)应用程序

    SealTalk-Android Android 应用 SealTalk 由 融云 RongCloud 出品. 特别注意 SealTalk 自从 1.3.14 版本起,CallLib 模块引用的音视频 ...

  6. 融云SDK实现类QQ即时通讯的前端开发

    一.概述 接到一个需求,需要在我们的客户端里实现类似QQ的社交功能,以方便玩家之间的沟通互动.我们的客户端是C++实现的,在开会讨论考虑到成本和时间问题,实现这个功能的任务交个了前端.为了简化说明,我 ...

  7. 基于融云的即时通讯开发(一)

    一.概述 现在的应用中,即时通讯功能已经很普遍了,从这篇文章开始,我们以第三方平台融云的服务为基础,研究一下如何开发一个具有及时通信功能的软件. 首先,进入融云的官网,地址如下: http://ron ...

  8. Flutter高仿微信-第36篇-单聊-语音通话

    Flutter高仿微信系列共59篇,从Flutter客户端.Kotlin客户端.Web服务器.数据库表结构.Xmpp即时通讯服务器.视频通话服务器.腾讯云服务器全面讲解. 详情请查看 效果图: 目前市 ...

  9. Flutter开发实战 高仿微信(二)发现页

    Flutter开发实战 高仿微信(二)发现页 Flutter开发实战 高仿微信(二)发现页 1.1 微信发现页面简述 1.2 APP框架优化 1.2.1 配置APP Logo和启动图片 1.2.2 配 ...

最新文章

  1. python资料库-python 资源库
  2. vue引用公用的头部和尾部文件。
  3. PCA主成分分析_特征创建(数据挖掘入门与实践-实验8)
  4. css的三个特性 背景透明设置
  5. uni-app小程序本地打包超过2M不能预览问题;小程序打包过大不能预览和真机调试;uni-app分包;
  6. python selenium与自动化
  7. android aar保存图片文件异常_我去!合并AAR时踩坑了!
  8. 保证Web数据库安全 认真把好七道关
  9. 微信小程序性别代码对应描述
  10. Git 基础(九)—— Failed to push some refs to git
  11. 像写C#一样编写java代码
  12. 带reportView的winform程序在部署安装的时需要装两个框架,一framework框架二就是reportviewer的安装包...
  13. sonar jacoco 覆盖率为0_接口测试代码覆盖率(jacoco)方案分享
  14. 河北省应用计算机模拟考试,河北省职称计算机考试模拟系统
  15. 百度文库下载工具(所有源码)
  16. 《统计学》第八版贾俊平第三章课后习题及答案总结
  17. 物联网建设,智慧城市道路智能交通解决方案
  18. 5G产业(一):5G超低延时噱头?
  19. 移动硬盘在计算机中不显示数据能恢复,移动硬盘无法访问提示'此卷不包含可识别的文件系统'怎么办?...
  20. Weekly Recap!相对不平淡的行情 相对冷淡的 DeFi 市场

热门文章

  1. lemke算法 matlab,lemke是什么意思
  2. javascript:dom的变动事件
  3. CSS 实现input自定义样式--文本框
  4. 数据恢复软件(列表)
  5. 计算机图形学(四)几何变换_3_矩阵逆变换
  6. ccc-Brief Introduction of Deep Learning-李宏毅(6)
  7. XMLViewer xml查看器
  8. 如何在linux内核中增加对应的Makefile和Kconfig选项?
  9. java算斜率_[Java教程]js用斜率判断鼠标进入div的四个方向
  10. 关于旅行商,哈密顿回路和NP问题的科普