一、安装目录分析
最近在做手机项目时,涉及很多本地文件管理方面的内容,比如用户的头像、下载的图片、视频等等,将这些文件缓存在本地,必须设计一个合理的组织方式,这样才能便于管理和后面的扩展及维护。这期间先后看了微视和美拍的本地文件组织方式,基本属于大家都在采用的方式,即一个大的cache文件夹,然后里面是按照类别划分的子文件夹,分别存放图片、视频等。随后又看了一下微信的组织方式,觉得微信的组织方式有点意思。同时也产生了几个疑问,到现在也没闹明白。
微信的安装目录位于Tencent文件夹内,该文件夹下面都是腾讯家的软件,什么手机qq、微信等应用的安装目录都在这里。其中MicroMsg文件夹就是微信的文件夹了,在使用微信过程中产生的图片、语音等缓存文件都分门别类的存在这里面。因为微信允许多帐号切换登录,所以不同帐号产生的图片、语音等缓存文件是分别存放的,本人分析应该是帐号加密后的密文做为文件夹名。
如上图所示为我的帐号文件夹,进入该文件夹后,如下图所示:
又是一堆文件夹,挨个点开看看吧,有些文件夹里面信息量还是很大的,比如image2文件夹,这里面存的是聊天过程中发送和接收的图片;sns文件夹,里面的文件都是没有扩展名的,不过将其拷贝至电脑,然后追加".png"或者".jpg"扩展名,然后就可以将其打开了,个人分析sns文件夹内存储的应该是用户浏览朋友圈时产生的图片;voice2文件夹,该文件夹保存的为所有聊天过程产生的语音文件,不管是一对一聊还是群聊,所有的语音文件都在这里。该语音文件为普通的amr文件,可以用播放器(比如暴风影音)播放,拷贝至电脑或者直接双击播放试试吧。上面提到的几个文件夹实际还有子文件夹,需要逐级打开才行。以voice2文件夹为例,实际打开voice2文件夹后,将会看到子文件夹,进入子文件夹后还会看到一级子文件夹,再次点击进入才是上面提到的amr语音文件。而子文件夹的命名规则表面上看很简单,为00至ff的十六进制命名,也就是最多有256个子文件夹。当用户收到一条语音文件后,该文件放在00至ff哪个子文件夹内,这肯定需要某种映射算法,该映射算法应该是在服务端执行,当然也有可能是在客户端这边执行,即客户端收到语音文件后,通过该映射算法,计算出该文件应该放在哪个文件下。这就是本人的第一个疑惑,没搞明白这个文件夹结构的设计机制。本人的另一个疑惑就是微信amr语音文件的命名规则,首先每一条语音文件文件名由30个字符组成,前三个字符为msg前缀,然后是一串由日期拼接的数字,格式为秒-时-分-月-日-年,但是随后的14个字符就让人迷糊了,个人分析可能是帐号加密的密文,但是接收自相同帐号的语音文件,这14个字符又有些不同,想来想去,始终没想明白。针对上面两条疑惑,期待哪位高人出现,指点一下,不胜感激。
二、微信主界面高仿
分析完微信的安装目录,下面来高仿一下微信主界面。本人对微信主界面比较有好感,主要是其界面设计的非常简介、美观,虽然其tab页面从原来底部移至现在顶部,刚开始着实让我适应了一段时间。现在网上已经有高人做了高仿微信界面工作,原文写的很详细,思路很清晰,并附了源码供下载研究,但是本人对其所使用的第三方PagerSlidingTabStrip类耿耿于怀,该类主要是通过自绘的方式实现tab的滑动与显示,总觉得这样做不够简洁。这个类包含了近600行代码,虽然灵活性非常好,因为是采用自绘方式,所以可以随意修改参数,以实现不同效果。但是因为内部逻辑较为复杂,并且注释很少,这样非常不利于代码维护。这样一个简单的功能,采用这么重的实现方式,实在得不偿失。于是本人尝试在原工程基础上重写tab页面切换方式,将原工程自绘方式改为调用android系统组件的方式实现。修改后,代码更加简洁,逻辑调用也更加容易理解,同时也方便扩展和维护。工程中标题栏部分代码,以及所有图片资源均为原工程内容,本人没有做过改动,均由细心的原工程作者编写及提供。
tab页面切换主要包含二部分,第一部分是页面显示,即点击标签页时,tab页面文字部分要显示点击状态,然后选中标签页时,文字要显示选中状态,其实就是将默认文字颜色变换两次,一次为点击颜色,一次为选中颜色;第二部分为页面切换,页面切换又可以分为手指左右滑动切换及点击tab文字切换。将这个思路理清楚后,功能上就好实现了。由于功能比较简单,所以这里就不做过多说明了。下面将主要代码贴出来,很多地方都有注释,比较好理解。首先为布局代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" tools:context=".MainActivity" > <RelativeLayout android:id="@+id/headLayout" android:layout_width="match_parent" android:layout_height="40dp" android:orientation="vertical"> <!-- 标题文字 --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" android:orientation="horizontal" > <!-- 聊天 --> <TextView android:id="@+id/chatTextView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="@string/chatText" android:textSize="16sp" android:textColor="@color/text_dark"/> <!-- 发现 --> <TextView android:id="@+id/foundTextView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="@string/foundText" android:textSize="16sp" android:textColor="@color/text_dark"/> <!-- 通讯录 --> <TextView android:id="@+id/contactsTextView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:text="@string/contactsText" android:textSize="16sp" android:textColor="@color/text_dark" /> </LinearLayout> <!-- 选中标识 --> <ImageView android:id="@+id/tabBottomLine" android:layout_width="wrap_content" android:layout_height="3dp" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:background="@color/text_green" android:contentDescription="@string/app_name" /> </RelativeLayout> <!-- 分隔线 --> <View android:id="@+id/dividerLine" android:layout_below="@+id/headLayout" android:background="@color/gray_light" android:layout_width="match_parent" android:layout_height="1dp"/> <!-- 页面容器 --> <android.support.v4.view.ViewPager android:id="@+id/viewPager" android:layout_below="@+id/dividerLine" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:flipInterval="300" android:persistentDrawingCache="animation" /> </RelativeLayout>下面为主工程代码:
package com.example.wechatsample; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import android.graphics.Color; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.util.DisplayMetrics; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewConfiguration; import android.view.Window; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; /** * 高仿微信主界面 */ public class MainActivity extends FragmentActivity implements OnClickListener, OnPageChangeListener { private TextView[] tabTextView = null; private ImageView tabBottomLine = null; private ViewPager viewPager = null; private List<Fragment> listFragments = null; private FragmentPagerAdapter fmPagerAdapter = null; private int tabWidth = 0; private int curTabIndex = VIEW_ID_CHAT; private static final int VIEW_ID_CHAT = 0; private static final int VIEW_ID_FOUND = 1; private static final int VIEW_ID_CONTACTS = 2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setOverflowShowingAlways(); // 初始化tab文字 tabTextView = new TextView[3]; tabTextView[VIEW_ID_CHAT] = (TextView)findViewById(R.id.chatTextView); tabTextView[VIEW_ID_FOUND] = (TextView)findViewById(R.id.foundTextView); tabTextView[VIEW_ID_CONTACTS] = (TextView)findViewById(R.id.contactsTextView); tabTextView[VIEW_ID_CHAT].setOnClickListener(this); tabTextView[VIEW_ID_FOUND].setOnClickListener(this); tabTextView[VIEW_ID_CONTACTS].setOnClickListener(this); // 初始化tab标识线 tabBottomLine = (ImageView)findViewById(R.id.tabBottomLine); RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams)tabBottomLine.getLayoutParams(); DisplayMetrics dm = getResources().getDisplayMetrics(); lParams.width = dm.widthPixels / 3; tabBottomLine.setLayoutParams(lParams); tabWidth = lParams.width; // 初始化页面 listFragments = new ArrayList<Fragment>(); listFragments.add(new ChatFragment()); listFragments.add(new FoundFragment()); listFragments.add(new ContactsFragment()); viewPager = (ViewPager)findViewById(R.id.viewPager); fmPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) { @Override public int getCount() { return listFragments.size(); } @Override public Fragment getItem(int index) { return listFragments.get(index); } }; viewPager.setAdapter(fmPagerAdapter); viewPager.setOnPageChangeListener(this); viewPager.setCurrentItem(VIEW_ID_CHAT); updateTabTextStatus(VIEW_ID_CHAT); } // 更新tab文字选中状态 private void updateTabTextStatus(int index) { for (TextView tv : tabTextView) { tv.setTextColor(Color.parseColor("#2c2c2c")); } tabTextView[index].setTextColor(Color.parseColor("#45c01a")); } // 切换tab选项 private void changeTabItem(int index) { Animation animation = new TranslateAnimation(curTabIndex*tabWidth , index*tabWidth, 0, 0); curTabIndex = index; animation.setFillAfter(true); animation.setDuration(300); tabBottomLine.startAnimation(animation); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { // 动画结束,更新文字选中状态 updateTabTextStatus(curTabIndex); } }); } @Override public void onPageSelected(int position) { // 左右滑动切换tab页 changeTabItem(position); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageScrollStateChanged(int state) { } @Override public void onClick(View v) { // 点击切换tab页 switch (v.getId()) { case R.id.chatTextView: tabTextView[VIEW_ID_CHAT].setTextColor(Color.parseColor("#8a8a8a")); viewPager.setCurrentItem(VIEW_ID_CHAT); break; case R.id.foundTextView: tabTextView[VIEW_ID_FOUND].setTextColor(Color.parseColor("#8a8a8a")); viewPager.setCurrentItem(VIEW_ID_FOUND); break; case R.id.contactsTextView: tabTextView[VIEW_ID_CONTACTS].setTextColor(Color.parseColor("#8a8a8a")); viewPager.setCurrentItem(VIEW_ID_CONTACTS); break; default: break; } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onMenuOpened(int featureId, Menu menu) { if (featureId == Window.FEATURE_ACTION_BAR && menu != null) { if (menu.getClass().getSimpleName().equals("MenuBuilder")) { try { Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE); m.setAccessible(true); m.invoke(menu, true); } catch (Exception e) { } } } return super.onMenuOpened(featureId, menu); } private void setOverflowShowingAlways() { try { ViewConfiguration config = ViewConfiguration.get(this); Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey"); menuKeyField.setAccessible(true); menuKeyField.setBoolean(config, false); } catch (Exception e) { e.printStackTrace(); } } }
工程运行后的效果图如下: