看到这个标题,是不是有很多人觉得这好像很简单,答对了一半,这是很简单的一个功能,可以有许多的实现方法。我这里讲解的是谷歌提倡的方法。
我重点讲解我独特的底部菜单栏的实现方式,这种方法重用及效率很高,而且还设计选择器的应用及图片控件的选择模式。
好了,废话我就不多说了,上图开讲:
1.反编译APK
首先的第一个难题就是,我不是设计师,不会PS,我是程序员啊,我哪有这些图片?
请某位高大上的设计师帮你做图片,可以,分成的时候你会给设计师一大笔钱,你觉得划算?况且那些高大上的设计师,会给你设计这些简单的小图标,一般到设计师那个级别的都很高傲。别指望设计师会免费为你提供任何有价值的服务,这个世界就是这样,独立合作的APP开发工作室会给设计师一大笔金钱,靠朋友关系请来的设计师,你没给钱,你觉得人家会认真做图片给你?也就是个友情价。(其实我是羡慕设计师,因为任何东西都是先设计出来才做了,我是个自卑的程序员,感觉任何行业最牛逼的就是设计师了,所有行业都受制于设计师)。
获取免费你的你想要开发的图片最简单,也是程序员经常使用的方法就是反编译APK,下面我们就来看看,怎么反编译网易新闻APP得到你想要的图片。
Ⅰ下载apktool
将下载的APK解压到一个单独的文件夹,如下所示:
本人放在E盘根目录下:
apktool包含这三个东西,一定要注意,上面那些是我反编译的其他APK,不属于apktool的资源。
Ⅱ下载网易新闻APP
这个不用我多说,百度下载android版本的网易APK。将你下载的APK改名为简单的名字后放入刚才的apktool目录中,如下图:
Ⅲ反编译网易APK
点击开始菜单输入cmd,回车:
切换到apktool目录中:
输入如下命令:apktool d -f 【wangyi.apk】 -o 【liyuanjinglyj】
第一个括号为你修改后网易APK的名称,要加后缀APK,第二个括号为存放反编译后的文件名,该文件创建在当前目录中。
得到如下结果就算反编译成功了:
我们来看看我们得到了什么东西,打开反编译后的文件夹liyuanjinglyj:
我们需要的文件全部都在res中,图片,布局文件,动画等资源,就像你开发项目一样在res目录下的所有文件,smali为反编译后的编程代码,如果你没学过Dalvik虚拟机语法,你是看不懂的,如果你想了解这方面知识,想在一些apk中植入代码,可以参考图灵之Android软件安全与逆向分析,这不属于我这章要讲的内容,如果有需要,我可以开设专栏讲解。
下面我们进入E:\apktool\liyuanjinglyj\res\drawable-xxhdpi-v4下找到我们需要的网易底部菜单栏图片复制到我们的工程项目中的drawable下。一共是10张。
2.设计底部菜单栏
我们应该用什么做下面的菜单呢?是弄RadioButton?NO,这不仅要消除RadioButton的原点,还要增加其底部的文字等,而且一旦你受阻碍于一个控件,你是无法扩展你任何想要的特效的。
这里我们用五个layout布局包裹每个菜单的按钮文字,把控件分开来做,这样可以方便扩展你所需要的功能,用RadioButton可以实现,可是这已经最小的控件了,你如果需要扩展怎么扩展?显然我选择的效率还是功能,都比RadioButton要高级的多。
编程的时候要注意,别把代码写死,这样就没有扩展的可能了。
Ⅰ创建底部菜单栏布局文件bottom_title_layout.xml:
version="1.0" encoding="utf-8"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0">
<LinearLayout
android:id="@+id/bottom_new_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:orientation="vertical"
android:gravity="center"
android:layout_weight="1">
<ImageButton
android:layout_width="32dp"
android:layout_height="32dp"
android:duplicateParentState="true"
android:clickable="false"
android:scaleType="fitXY"
android:background="@drawable/news_selector_background" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:clickable="false"
android:text="@string/bottom_title_news_string"
android:textColor="@color/bottom_title_text_color"/>
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_read_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:orientation="vertical"
android:gravity="center"
android:layout_weight="1">
<ImageButton
android:layout_width="32dp"
android:layout_height="32dp"
android:duplicateParentState="true"
android:clickable="false"
android:scaleType="fitXY"
android:background="@drawable/read_selector_background"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:clickable="false"
android:text="@string/bottom_title_read_string"
android:textColor="@color/bottom_title_text_color"/>
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_video_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:orientation="vertical"
android:gravity="center"
android:layout_weight="1">
<ImageButton
android:layout_width="32dp"
android:layout_height="32dp"
android:duplicateParentState="true"
android:clickable="false"
android:scaleType="fitXY"
android:background="@drawable/video_selector_background" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:clickable="false"
android:text="@string/bottom_title_video_string"
android:textColor="@color/bottom_title_text_color"/>
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_lamp_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:orientation="vertical"
android:gravity="center"
android:layout_weight="1">
<ImageButton
android:layout_width="32dp"
android:layout_height="32dp"
android:duplicateParentState="true"
android:clickable="false"
android:scaleType="fitXY"
android:background="@drawable/lamp_selector_background" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:clickable="false"
android:text="@string/bottom_title_lamp_string"
android:textColor="@color/bottom_title_text_color"/>
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_pc_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:orientation="vertical"
android:gravity="center"
android:layout_weight="1">
<ImageButton
android:layout_width="32dp"
android:layout_height="32dp"
android:duplicateParentState="true"
android:clickable="false"
android:scaleType="fitXY"
android:background="@drawable/pc_selector_background" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:clickable="false"
android:text="@string/bottom_title_pc_string"
android:textColor="@color/bottom_title_text_color"/>
</LinearLayout>
</LinearLayout>
解释1:
每一个菜单都是用imageButton和TextView组成,用他们的父LinearLayout拦截他们的点击事件,这样子控件的可以同时相应他们的点击事件,
要想父控件相应子控件,而点击子控件就是跟点击父控件一样的效果的时候,必须同时设置三个属性,方可以实现此功能。
一是必须设置父控件可以被点击,如下:
android:clickable="true"
二是必须设置子控件会跟随父控件的状态,也就是父控件被点击,子控件也是被点击:
android:duplicateParentState="true"
三是必须设置子控件不可以被点击,这样保证单独点击子控件,子控件会将点击事件传递给父控件处理,不然点击事件会被子控件拦截,那么另一个子控件就不会响应了:
android:clickable="false"
解释2:
这里面所有子控件都有选择器,我们先从文字选择器开始讲解,因为文字被点击,被按下,被选择,这五个的效果都是一样的,所以这里只用了一个选择器,也就是bottom_title_text_color.xml:
version="1.0" encoding="utf-8"
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="#CD0000" />
<item android:state_focused="true" android:color="#CD0000" />
<item android:state_pressed="true" android:color="#CD0000"/>
<item android:color="#89683B" />
</selector>
android:state_selected="true"此为选中时候的文字颜色
android:state_focused="true"此为非触摸式下获取焦点时候的文字颜色,也就是你按住屏幕某个位子,将手指移动到该处时候的反映。
android:state_pressed="true"此为按下时候的文字颜色
最下面无任何前提的属于默认颜色。这里上三个偏红,默认偏灰。
文字颜色选择器应该放到res/color中。
这里有必要解释一下的是冲突,什么是选择冲突?
当你点击某个按钮的时候,这个时候此按钮不仅被按下,同时为选中,也获取到了焦点。那么三者合一对于文字来说很好理解,这三个一样嘛,无所谓,可当这是图片选择器的时候,三种选择样式不一样就会有冲突,这个是怎么解决的呢?
默认程序是选择你选择器中写的最前面的那个作为显示的,所以你编程的时候,最好考虑清楚冲突的问题,免得写程序的时候,明明写的选择器却没有达到你想要的效果,你又不知道原因。
解释3:
新闻背景选择器news_selector_background.xml代码:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/news_red"/>
<item android:state_selected="true" android:drawable="@drawable/news_red"/>
<item android:state_enabled="true" android:drawable="@drawable/news_black"/>
</selector>
其他背景选择器和这个一样,就不一一描述了。
上面讲解过文字颜色选择器,这个图片选择器原理是一模一样的。
解释4:
android:scaleType="fitXY"
将图片边界缩放,以适应视图边界时的可选项。
在视图中使图像居中,不执行缩放。 在 XML 中可以使用的语法: android:scaleType="center"。
均衡的缩放图像(保持图像原始比例),使图片的两个坐标(宽、高)都大于等于 相应的视图坐标(负的内边距)。图像则位于视图的中央。 在 XML 中可以使用的语法:android:scaleType="centerCrop"。
均衡的缩放图像(保持图像原始比例),使图片的两个坐标(宽、高)都小于等于 相应的视图坐标(负的内边距)。图像则位于视图的中央。 在 XML 中可以使用的语法:android:scaleType="centerInside"。
使用 CENTER 方式缩放图像。 在 XML 中可以使用的语法: android:scaleType="fitCenter"。
使用 END 方式缩放图像。 在 XML 中可以使用的语法: android:scaleType="fitEnd"。
使用 START 方式缩放图像。 在 XML 中可以使用的语法:android:scaleType="fitStart"。
使用 FILL 方式缩放图像。 在 XML 中可以使用的语法: android:scaleType="fitXY"。
绘制时,使用图像矩阵方式缩放。图像矩阵可以通过 setImageMatrix(Matrix) 设置。在 XML 中可以使用的语法: android:scaleType="matrix"。
这里很显然,是让图片适应我所设置的大小。
解释5:
注意在Android编程是要考虑手机尺寸问题的,什么单位会自适应手机尺寸呢?
dp:图片自适应手机尺寸,图片只要设置这个属性,在大手机上,小手机上会等比例缩放。
sp:文字自适应手机尺寸,文字只要设置这个属性,在大手机上,小手机上会等比例缩放。
如果手机尺寸差别大到平板和手机的区别,这个时候是建立特定手机图片文件夹sw320dp、sw600dp、sw720dp等屏幕的基本尺寸。
解释6:
新闻我叫news,阅读我叫read,视听我叫video,发现我叫lamp(灯泡),我叫pc。防止误解。
Ⅱ创建5个用于切换的fragment
分别创建它们的布局文件,用于显示和区分,如下:
每个布局里面放个TextView用于区分就可以了,今天重点讲解实现,并不具体实现功能。
如下代码:
public class NewFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.news_fragment_main_layout,container,false);
return view;
}
}
布局代码:
version="1.0" encoding="utf-8"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="32sp"
android:text="wo shi news"/>
</LinearLayout>
Ⅲ主布局文件中加入4个fragment
主布局代码如下activity_main:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/fragement_news"
android:name="com.example.liyuanjing.btmmenufragment.NewFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/fragement_read"
android:name="com.example.liyuanjing.btmmenufragment.ReadFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/fragement_video"
android:name="com.example.liyuanjing.btmmenufragment.VideoFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/fragement_lamp"
android:name="com.example.liyuanjing.btmmenufragment.LampFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/fragement_pc"
android:name="com.example.liyuanjing.btmmenufragment.PcFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1" />
<include layout="@layout/bottom_title_layout"/>
</LinearLayout>
这个里面不需要解释相信大家都看的懂了,
现在就是MainActivity里面的代码了,要想使用fragment,必须使MainActivity继承自FragmentActivity里面。
其原因是里面包含了Fragment运作的FragmentManager接口的实现类 FragmentManagerImpl ,由这个类管理所有Fragment的显示、隐藏。而fragmentTransaction对fragment进行添加,替换移除等功能。
使用fragmentManager.beginTransaction()获取FragmentTransaction实例,FragmentManager是在activity中与fragment对象进行互操作的接口。在继承自FragmentActivity中通过getSupportFragmentManager();获得。
主MainActivity.class代码如下:
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;
public class MainActivity extends FragmentActivity implements View.OnClickListener{
private LinearLayout newsLayout;
private LinearLayout readLayout;
private LinearLayout videoLayout;
private LinearLayout lampLayout;
private LinearLayout pcLayout;
private Fragment[] mFragments;
private FragmentManager fragmentManager;
private FragmentTransaction fragmentTransaction;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.newsLayout = (LinearLayout) findViewById(R.id.bottom_new_layout);
this.readLayout = (LinearLayout) findViewById(R.id.bottom_read_layout);
this.videoLayout = (LinearLayout) findViewById(R.id.bottom_video_layout);
this.lampLayout = (LinearLayout) findViewById(R.id.bottom_lamp_layout);
this.pcLayout = (LinearLayout) findViewById(R.id.bottom_pc_layout);
this.newsLayout.setOnClickListener(this);
this.readLayout.setOnClickListener(this);
this.videoLayout.setOnClickListener(this);
this.lampLayout.setOnClickListener(this);
this.pcLayout.setOnClickListener(this);
this.newsLayout.setSelected(true);//选择器就是这样用的,设置false,或true来改变选择
mFragments = new Fragment[5];
this.fragmentManager = getSupportFragmentManager();//获取fragmentManager实例,通过fragmentManager管理fragment
mFragments[0] = fragmentManager.findFragmentById(R.id.fragement_news);//获取fragment实例
mFragments[1] = fragmentManager.findFragmentById(R.id.fragement_read);
mFragments[2] = fragmentManager.findFragmentById(R.id.fragement_video);
mFragments[3] = fragmentManager.findFragmentById(R.id.fragement_lamp);
mFragments[4] = fragmentManager.findFragmentById(R.id.fragement_pc);
fragmentTransaction = fragmentManager.beginTransaction()//获取fragmentTransaction实例
.hide(mFragments[0]).hide(mFragments[1]).hide(mFragments[2]).hide(mFragments[3]).hide(mFragments[4]);//通过hide显示隐藏当前fragment
fragmentTransaction.show(mFragments[0]).commit();//通过show显示当前fragment,通过commit()提交刚才的操作。commit()和数据库的相似,你可以用fragmentT//ransaction做一大堆操作后一起提交。
}
@Override
public void onClick(View v) {
fragmentTransaction = fragmentManager.beginTransaction()
.hide(mFragments[0]).hide(mFragments[1]).hide(mFragments[2]).hide(mFragments[3]).hide(mFragments[4]);
switch (v.getId()) {
case R.id.bottom_new_layout:
this.newsLayout.setSelected(true);//设置选择器
this.readLayout.setSelected(false);
this.videoLayout.setSelected(false);
this.lampLayout.setSelected(false);
this.pcLayout.setSelected(false);
fragmentTransaction.show(mFragments[0]).commit();//显示第一fragment
break;
case R.id.bottom_read_layout:
this.newsLayout.setSelected(false);
this.readLayout.setSelected(true);
this.videoLayout.setSelected(false);
this.lampLayout.setSelected(false);
this.pcLayout.setSelected(false);
fragmentTransaction.show(mFragments[1]).commit();
break;
case R.id.bottom_video_layout:
this.newsLayout.setSelected(false);
this.readLayout.setSelected(false);
this.videoLayout.setSelected(true);
this.lampLayout.setSelected(false);
this.pcLayout.setSelected(false);
fragmentTransaction.show(mFragments[2]).commit();
break;
case R.id.bottom_lamp_layout:
this.newsLayout.setSelected(false);
this.readLayout.setSelected(false);
this.videoLayout.setSelected(false);
this.lampLayout.setSelected(true);
this.pcLayout.setSelected(false);
fragmentTransaction.show(mFragments[3]).commit();
break;
case R.id.bottom_pc_layout:
this.newsLayout.setSelected(false);
this.readLayout.setSelected(false);
this.videoLayout.setSelected(false);
this.lampLayout.setSelected(false);
this.pcLayout.setSelected(true);
fragmentTransaction.show(mFragments[4]).commit();
break;
default:
break;
}
}
}
上面就是今天网易底部菜单栏的实现,很简单是不是。不过设计的知识点有点多,单个知识点并没有细细讲解,只是简单的提到,如有需要还请百度你要的详细答案。
本文的源代码在:
测试结果: