关于abiFilters的使用

前言
*近项目中遇到了要使用opencv的情况,涉及到了abi兼容的选择。因为如果全部都适配的话,包很大,这样兼容那些用户数*少的cpu就很不划算,所以我只适配了armeabi-v7a这一个。但是今天在x64-v8a的模拟器上看的时候,提示我的library.so文件找不到,我记得这个应该是向下兼容的,但是出现这种情况很奇怪,于是我就在网上找了找答案。

解决方法:abiFilters
在app的gradle的defaultConfig里面加上这么一句

ndk {
abiFilters “armeabi-v7a” // 指定要ndk需要兼容的架构(这样其他依赖包里mips,x86,armeabi,arm-v8之类的so会被过滤掉)
}

这句话的意思就是指定ndk需要兼容的架构,把除了v7a以外的兼容包都过滤掉,只剩下一个v7a的文件夹。用了这个方法之后,确实解决了问题。这就是解决方法。

具体分析
其实这个方法我开始是很奇怪的,我明明没有指定其他的兼容框架,为什么会需要一个过滤。我打来了apk的包,找到了里面的lib目录,发现里面有很多的兼容目录,然后看到里面目录里面的是一个fresco的.so文件。也就是说,fresco做了各个平台的兼容,所以它创建了各个兼容平台的目录。因为只要出现了这个目录,系统就只会在这个目录里找.so文件而不会遍历其他的目录,所以就出现了之前找不到.so文件的情况(因为其他目录没有我的.so文件)。

总结
为了决定*后适配的abi版本,我下载了排行前几名的app,然后打开之后发现,他们基本上只适配了一个armeabi,少数会再加上v7a。我了解到的情况是armeabi性能较差,但是兼容性*好,v7a对于浮点计算的cpu来说性能更好,不兼容不支持浮点运算的cpu。我想到的是目前的手机cpu*大多数应该是支持浮点运算的,而且安卓从2.2开始就支持v7a,所以v7a的兼容性应该也不是问题。(不知道对不对,谁能明确一下的,恳请指正)
无论如何,abiFilters还是应该添加的。

Android Studio gardle 配置 ndk 指定 ABI: abiFilters 详解

一、ABI 是什么
ABI 是 Application Binary Interface 的缩写。

不同 Android 手机使用不同的 CPU,因此支持不同的指令集。CPU 与指令集的每种组合都有其自己的应用二进制界面(或 ABI)。 ABI 可以非常精确地定义应用的机器代码在运行时如何与系统交互。 您必须为应用要使用的每个 CPU 架构指定 ABI。

典型的 ABI 包含以下信息:

机器代码应使用的 CPU 指令集。
运行时内存存储和加载的字节顺序。
可执行二进制文件(例如程序和共享库)的格式,以及它们支持的内容类型。
用于解析内容与系统之间数据的各种约定。这些约定包括对齐限制,以及系统如何使用堆栈和在调用函数时注册。
运行时可用于机器代码的函数符号列表 – 通常来自非常具体的库集。
二、如何在 gardle 中配置
默认情况下,cmake 会输出 4 种 ABI(”armeabi-v7a” , “arm64-v8a”, “x86”, “x86_64″),如下所示:

%title插图%num

我们也可以通过 abiFilters 来指定我们需要的 ABI:

%title插图%num

abiFilters “armeabi”, “armeabi-v7a” , “arm64-v8a”, “x86”, “x86_64”, “mips”, “mips64”

三、支持的 ABI 详解

%title插图%num

Android底部导航栏,三种风格和实现

一、效果图展示

%title插图%num

如果动图没有动的话,也可以看下面这个静态图

%title插图%num

以下挨个分析每个的实现,这里只做简单的效果展示,大家可以基于目前代码做二次开发。

二、BottomNavigationView
这是 Google 给我们提供的一个专门用于底部导航的 View,你只需要在新建 Activity 的时候选择 “Bottom Navigation Activity”,IDE 就会自动使用 BottomNavigationView 帮你生成好相应的代码了。

1. 在 xml 中使用
<android.support.design.widget.BottomNavigationView
android:id=”@+id/navigation”
android:layout_width=”0dp”
android:layout_height=”wrap_content”
android:layout_marginEnd=”0dp”
android:layout_marginStart=”0dp”
android:background=”?android:attr/windowBackground”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
app:menu=”@menu/navigation” />
这里面唯一要注意的就是 app:menu 属性了,它指定了你的导航栏显示的页面菜单是怎样的。

2. menu 的布局文件
<?xml version=”1.0″ encoding=”utf-8″?>
<menu xmlns:android=”http://schemas.android.com/apk/res/android”>

<item
android:id=”@+id/navigation_home”
android:icon=”@drawable/ic_home_black_24dp”
android:title=”@string/title_home” />

<item
android:id=”@+id/navigation_dashboard”
android:icon=”@drawable/ic_dashboard_black_24dp”
android:title=”@string/title_dashboard” />

<item
android:id=”@+id/navigation_notifications”
android:icon=”@drawable/ic_notifications_black_24dp”
android:title=”@string/title_notifications” />

</menu>
3. 在 Activity 中调用
private TextView mTextMessage;

private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
mTextMessage.setText(R.string.title_home);
return true;
case R.id.navigation_dashboard:
mTextMessage.setText(R.string.title_dashboard);
return true;
case R.id.navigation_notifications:
mTextMessage.setText(R.string.title_notifications);
return true;
}
return false;
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_style1);

mTextMessage = findViewById(R.id.message);
BottomNavigationView navigation = findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
}
这里的演示 code 都是 IDE 自动生成的,由于 BottomNavigationView 目前我还没有在项目中实际使用过,这里不做过多分析,使用起来不难,以上代码已经足以满足我们的基本使用要求了。

三、RadioGroup + ViewPager
这是一种比较常见了的,下面 4 个 tab 的导航按钮,可以切换不同的页面,这里页面使用了 ViewPager + Fragment 的组合,实现了滑动的页面效果,也可以不使用 ViewPager,这个根据产品的定义来使用即可。

1. 布局文件
<?xml version=”1.0″ encoding=”utf-8″?>
<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”
tools:context=”.style2.Style2Activity”>

<android.support.v4.view.ViewPager
android:id=”@+id/fragment_vp”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:layout_above=”@+id/tabs_rg” />

<RadioGroup
android:id=”@+id/tabs_rg”
android:layout_width=”match_parent”
android:layout_height=”56dp”
android:layout_alignParentBottom=”true”
android:background=”#dcdcdc”
android:orientation=”horizontal”>

<RadioButton
android:id=”@+id/today_tab”
style=”@style/Custom.TabRadioButton”
android:checked=”true”
android:drawableTop=”@drawable/tab_sign_selector”
android:text=”今日” />

<RadioButton
android:id=”@+id/record_tab”
style=”@style/Custom.TabRadioButton”
android:drawableTop=”@drawable/tab_record_selector”
android:text=”记录” />

<RadioButton
android:id=”@+id/contact_tab”
style=”@style/Custom.TabRadioButton”
android:drawableTop=”@drawable/tab_contact_selector”
android:text=”通讯录” />

<RadioButton
android:id=”@+id/settings_tab”
style=”@style/Custom.TabRadioButton”
android:drawableTop=”@drawable/tab_setting_selector”
android:text=”设置” />
</RadioGroup>
</RelativeLayout>
2. Activity 类
public class Style2Activity extends AppCompatActivity {

private ViewPager mViewPager;
private RadioGroup mTabRadioGroup;

private List<Fragment> mFragments;
private FragmentPagerAdapter mAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_style2);
initView();
}

private void initView() {
// find view
mViewPager = findViewById(R.id.fragment_vp);
mTabRadioGroup = findViewById(R.id.tabs_rg);
// init fragment
mFragments = new ArrayList<>(4);
mFragments.add(BlankFragment.newInstance(“今日”));
mFragments.add(BlankFragment.newInstance(“记录”));
mFragments.add(BlankFragment.newInstance(“通讯录”));
mFragments.add(BlankFragment.newInstance(“设置”));
// init view pager
mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager(), mFragments);
mViewPager.setAdapter(mAdapter);
// register listener
mViewPager.addOnPageChangeListener(mPageChangeListener);
mTabRadioGroup.setOnCheckedChangeListener(mOnCheckedChangeListener);
}

@Override
protected void onDestroy() {
super.onDestroy();
mViewPager.removeOnPageChangeListener(mPageChangeListener);
}

private ViewPager.OnPageChangeListener mPageChangeListener = new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {
RadioButton radioButton = (RadioButton) mTabRadioGroup.getChildAt(position);
radioButton.setChecked(true);
}

@Override
public void onPageScrollStateChanged(int state) {

}
};

private RadioGroup.OnCheckedChangeListener mOnCheckedChangeListener = new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
for (int i = 0; i < group.getChildCount(); i++) {
if (group.getChildAt(i).getId() == checkedId) {
mViewPager.setCurrentItem(i);
return;
}
}
}
};

private class MyFragmentPagerAdapter extends FragmentPagerAdapter {

private List<Fragment> mList;

public MyFragmentPagerAdapter(FragmentManager fm, List<Fragment> list) {
super(fm);
this.mList = list;
}

@Override
public Fragment getItem(int position) {
return this.mList == null ? null : this.mList.get(position);
}

@Override
public int getCount() {
return this.mList == null ? 0 : this.mList.size();
}
}

}
这里唯一注意点的就是两个监听事件,要实现底部导航按钮和页面的联动。

四、带页面跳转功能的底部导航
很多 APP 的底部导航栏中间有一个很大的按钮,点击后通常是打开一个新的页面,这里我们要实现的就是这种底部导航。
依旧是使用 RadioGroup 来做,只不过中间一个 tab 我们先用一个空的 View 来占位,然后在这个 View 的位置放置一个较大的按钮来覆盖住。

1. 布局文件
<?xml version=”1.0″ encoding=”utf-8″?>
<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”
tools:context=”.style3.Style3Activity”>

<FrameLayout
android:id=”@+id/fragment_container”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:layout_above=”@+id/tabs_rg” />

<RadioGroup
android:id=”@+id/tabs_rg”
android:layout_width=”match_parent”
android:layout_height=”56dp”
android:layout_alignParentBottom=”true”
android:background=”#dcdcdc”
android:orientation=”horizontal”>

<RadioButton
android:id=”@+id/today_tab”
style=”@style/Custom.TabRadioButton”
android:checked=”true”
android:drawableTop=”@drawable/tab_sign_selector”
android:text=”今日” />

<RadioButton
android:id=”@+id/record_tab”
style=”@style/Custom.TabRadioButton”
android:drawableTop=”@drawable/tab_record_selector”
android:text=”记录” />

<View style=”@style/Custom.TabRadioButton” />

<RadioButton
android:id=”@+id/contact_tab”
style=”@style/Custom.TabRadioButton”
android:drawableTop=”@drawable/tab_contact_selector”
android:text=”通讯录” />

<RadioButton
android:id=”@+id/settings_tab”
style=”@style/Custom.TabRadioButton”
android:drawableTop=”@drawable/tab_setting_selector”
android:text=”设置” />
</RadioGroup>

<ImageView
android:id=”@+id/sign_iv”
android:layout_width=”80dp”
android:layout_height=”80dp”
android:layout_alignParentBottom=”true”
android:layout_centerHorizontal=”true”
android:background=”@android:color/transparent”
android:src=”@mipmap/sign” />
</RelativeLayout>
2. Activity 类
public class Style3Activity extends AppCompatActivity {

private RadioGroup mTabRadioGroup;
private SparseArray<Fragment> mFragmentSparseArray;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_style3);
initView();
}

private void initView() {
mTabRadioGroup = findViewById(R.id.tabs_rg);
mFragmentSparseArray = new SparseArray<>();
mFragmentSparseArray.append(R.id.today_tab, BlankFragment.newInstance(“今日”));
mFragmentSparseArray.append(R.id.record_tab, BlankFragment.newInstance(“记录”));
mFragmentSparseArray.append(R.id.contact_tab, BlankFragment.newInstance(“通讯录”));
mFragmentSparseArray.append(R.id.settings_tab, BlankFragment.newInstance(“设置”));
mTabRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
// 具体的fragment切换逻辑可以根据应用调整,例如使用show()/hide()
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
mFragmentSparseArray.get(checkedId)).commit();
}
});
// 默认显示*个
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container,
mFragmentSparseArray.get(R.id.today_tab)).commit();
findViewById(R.id.sign_iv).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(Style3Activity.this, SignActivity.class));
}
});
}

}
注意:

如果这里你也想使用 ViewPager 来展示 Fragment 的话,一定要注意这里的 RadioGroup 中间有一个占位的 View,即两者的监听事件里,实现联动时要考虑多个这个 View 的存在。

Android EditText设置边框

Android EditText设置边框
简介
Android应用程序中给EditText设置边框。

效果图:

%title插图%num

快速开始
在res/drawable目录下新建样式文件 edit_background.xml。
<?xml version=”1.0″ encoding=”utf-8″?>
<layer-list xmlns:android=”http://schemas.android.com/apk/res/android”>

<item>
<shape
android:shape=”rectangle”>
<solid android:color=”#efefef”/>
<corners android:radius=”5dp”/>
<stroke
android:width=”1dp”
android:color=”#505050″/>
</shape>
</item>

布局文件中使用边框效果,/res/layout/activity_edit_text.xml。
android:background=”@drawable/edit_background”

<?xml version=”1.0″ encoding=”utf-8″?>
<android.support.constraint.ConstraintLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
tools:context=”.EditTextActivity”>

<TextView
android:id=”@+id/name_label”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”账号:”
android:textSize=”18sp”
android:textColor=”#353535″
android:layout_marginTop=”60dp”
android:layout_marginStart=”60dp”
app:layout_constraintTop_toTopOf=”parent”
app:layout_constraintLeft_toLeftOf=”parent”/>

<EditText
android:id=”@+id/edit_name”
android:layout_width=”200dp”
android:layout_height=”40dp”
android:hint=”请输入账号”
android:gravity=”center”
android:inputType=”number”
android:background=”@drawable/edit_background”
android:layout_marginStart=”10dp”
app:layout_constraintTop_toTopOf=”@id/name_label”
app:layout_constraintBottom_toBottomOf=”@id/name_label”
app:layout_constraintLeft_toRightOf=”@id/name_label”/>

<TextView
android:id=”@+id/pwd_label”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”密码:”
android:textSize=”18sp”
android:textColor=”#353535″
android:layout_marginTop=”40dp”
android:layout_marginStart=”60dp”
app:layout_constraintTop_toBottomOf=”@id/name_label”
app:layout_constraintLeft_toLeftOf=”parent”/>

<EditText
android:id=”@+id/edit_pwd”
android:layout_width=”200dp”
android:layout_height=”40dp”
android:hint=”请输入密码”
android:gravity=”center”
android:inputType=”textPassword”
android:background=”@drawable/edit_background”
android:layout_marginStart=”10dp”
app:layout_constraintTop_toTopOf=”@id/pwd_label”
app:layout_constraintBottom_toBottomOf=”@id/pwd_label”
app:layout_constraintLeft_toRightOf=”@id/pwd_label”/>

<Button
android:id=”@+id/btn_login”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”ok”
android:layout_marginTop=”30dp”
android:layout_marginStart=”110dp”
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintTop_toBottomOf=”@id/pwd_label”/>

<Button
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Cancel”
android:layout_marginStart=”50dp”
app:layout_constraintTop_toTopOf=”@id/btn_login”
app:layout_constraintLeft_toRightOf=”@id/btn_login”/>

</android.support.constraint.ConstraintLayout>

 

【android】NavigationView控件的使用

这是Google在Android5.0之后推出的一个控件,兼容到Android2.1,代替之前自己做抽屉菜单,简单方便。NavigationView整体分为上下两个部分,上部分叫做HeaderLayout,下面的点击项都是menu。

%title插图%num

NavigationView的使用方法
1、写布局文件
整体布局
<android.support.v4.widget.DrawerLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmls:app=”http://schemas.android.com/apk/res-auto”
xmls:tools=”http://schemas.android.com/tools”
android:id=”@_id/item_layout”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:fitsSystemWindows=”true”
tools:openDrawer=”start”>

<include
layout=”@layout/weather_layout”
android:layout_width=”match_parent”
android:layout_height=”match_parent” />

<android.support.design.widget.NavigationView
android:id=”@+id/nav_view”
android:layout_width=”wrap_content”
android:layout_height=”match_parent”
android:layout_gravity=”start”
android:fitsSystemWindows=”true”
app:headerLayout=”@layout/nav_header_main”
app:menu=”@menu/activity_main_drawer”/>

</android.support.v4.widget.DrawerLayout>

代码解释:*外层是个DrawerLayout,

headerLayout
看一下headerLayout(app:headerLayout=”@layout/nav_header_main”)的代码:

<?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=”160dp”
android:background=”#81c0c0″
android:gravity=”bottom”
android:paddingBottom=”@dimen/activity_vertical_margin”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:orientation=”vertical”>

<ImageView
android:id=”@+id/imageView”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:paddingTop=”16dp”
android:src=”@mipmap/cloud” />

<TextView
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:paddingTop=”16dp”
android:text=”Weather”
android:textAppearance=”@style/TextAppearance.AppCompat.Body1″/>

<TextView
android:id=”@+id/textView”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Designed by Wangmj” />

</LinearLayout>

完成之后是下面这个样子

%title插图%num
menu
再看一下menu(app:menu=”@menu/activity_main_drawer”) 的代码:

<?xml version=”1.0″ encoding=”utf-8″?>
<menu xmlns:android=”http://schemas.android.com/apk/res/android”>

<group android:checkableBehavior=”single”>
<item
android:id=”@+id/select_city”
android:icon=”@mipmap/checklist_64px”
android:title=”选择城市” />
</group>

<item android:title=”Communicate”>
<menu>
<item
android:id=”@+id/share”
android:icon=”@mipmap/share_48px”
android:title=”分享” />
</menu>
</item>

</menu>

这里可以分组,虽然我写的组里面只有一个item,现在还没有那么多功能,慢慢加啦;组与组之间会有分割线,这里只有一个组,所以没有。

%title插图%num
content
接下来再来看一下content的布局文件:

<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
xmlns:app=”http://schemas.android.com/apk/res-auto”
android:orientation=”vertical”>

<RelativeLayout
android:id=”@+id/top”
android:layout_width=”match_parent”
android:layout_height=”50dp”
android:background=”#408080″>

<android.support.v7.widget.Toolbar
android:id=”@+id/menu_button”
android:layout_width=”30dp”
android:layout_height=”30dp”
android:layout_centerVertical=”true”
android:layout_marginLeft=”10dp”/>
<!–android:background=”@mipmap/home”–>

<TextView
android:id=”@+id/city_name”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_centerInParent=”true”
android:textColor=”#fff”
android:textSize=”24dp”
android:text=”city_name”/>
<Button
android:id=”@+id/refresh_weather”
android:layout_width=”30dp”
android:layout_height=”30dp”
android:layout_alignParentRight=”true”
android:layout_centerVertical=”true”
android:layout_marginRight=”10dp”
android:background=”@mipmap/refresh”/>
</RelativeLayout>

<RelativeLayout
android:layout_width=”match_parent”
android:layout_height=”0dp”
android:layout_weight=”1″
android:background=”#81C0C0″>

<TextView
android:id=”@+id/publish_text”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignParentRight=”true”
android:layout_marginRight=”10dp”
android:layout_marginTop=”10dp”
android:textColor=”#FFF”
android:textSize=”18sp”/>

<LinearLayout
android:id=”@+id/weather_info_layout”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_centerInParent=”true”
android:orientation=”vertical”>

<TextView
android:id=”@+id/current_data”
android:layout_width=”wrap_content”
android:layout_height=”40dp”
android:gravity=”center”
android:textColor=”#fff”
android:textSize=”18sp”/>

<TextView
android:id=”@+id/weather_desp”
android:layout_width=”wrap_content”
android:layout_height=”60sp”
android:layout_gravity=”center_horizontal”
android:gravity=”center”
android:textColor=”#fff”
android:textSize=”40sp”/>

<LinearLayout
android:layout_width=”wrap_content”
android:layout_height=”60dp”
android:layout_gravity=”center_horizontal”
android:orientation=”horizontal”>

<TextView
android:id=”@+id/temp1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_weight=”3″
android:layout_gravity=”center_vertical”
android:textColor=”#fff”
android:textSize=”20sp”/>

<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center_vertical”
android:layout_marginLeft=”10dp”
android:layout_marginRight=”10dp”
android:layout_weight=”1″
android:text=”~”
android:textColor=”#fff”
android:textSize=”20sp”/>

<TextView
android:id=”@+id/temp2″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_weight=”3″
android:layout_gravity=”center_vertical”
android:textColor=”#fff”
android:textSize=”20sp”/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</LinearLayout>

代码解释:Toolbar底下的内容都不用管,主要是Toolbar和NavigationView有关系,所以只用看Toobar。

2.点击事件
public class WeatherActivity extends Activity implements NavigationView.OnNavigationItemSelectedListener{

/**
* 菜单按钮
*/
private Toolbar menuButton;
/**
* 菜单布局
*/
private DrawerLayout itemLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.item_layout);
itemLayout=(DrawerLayout)findViewById(R.id.item_layout);
menuButton=(Toolbar) findViewById(R.id.menu_button);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, itemLayout, menuButton,R.string.navigation_drawer_open, R.string.navigation_drawer_close);
itemLayout.setDrawerListener(toggle);
toggle.syncState();
NavigationView navigationView=(NavigationView)findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);

}

@Override
public boolean onNavigationItemSelected(MenuItem item) {
int id=item.getItemId();
switch (id){
case R.id.select_city:
Intent intent=new Intent(this,ChooseAreaActivity.class);
intent.putExtra(“from_weather_activity”,true);
startActivity(intent);
finish();
break;
case R.id.share:
Toast.makeText(WeatherActivity.this, “正在开发中…”, Toast.LENGTH_SHORT).show();
break;
//可以更多,只要在menu的布局文件里声明。
}
return false;
}
}

结束。

Android 自己实现 NavigationView [Design Support Library]

Android 自己实现 NavigationView [Design Support Library]

一、概述
Google I/O 2015 给大家带来了Android Design Support Library,对于希望做md风格的app的来说,简直是天大的喜讯了~大家可以通过Android Design Support Library该文章对其进行了解,也可以直接在github上下载示例代码运行学习。为了表达我心中的喜悦,我决定针对该库写一系列的文章来分别介绍新增加的控件。

ok,那么首先介绍的就是NavigationView。

注意下更新下as的SDK,然后在使用的过程中,在build.gradle中添加:

compile ‘com.android.support:design:22.2.0’
1
在md风格的app中,例如如下风格的侧滑菜单非常常见:

%title插图%num

在之前的设计中,你可能需要考虑如何去布局实现,例如使用ListView;再者还要去设计Item的选中状态之类~~

but,现在,google提供了NavigationView,你只需要写写布局文件,这样的效果就ok了,并且兼容到Android 2.1,非常值得去体验一下。接下来我们来介绍如何去使用这个NavigationView!

二、使用
使用起来very simple ,主要就是写写布局文件~

(一)布局文件
<?xml version=”1.0″ encoding=”utf-8″?>
<android.support.v4.widget.DrawerLayout
android:id=”@+id/id_drawer_layout”
xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:fitsSystemWindows=”true”
>

<RelativeLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<android.support.v7.widget.Toolbar
android:id=”@+id/id_toolbar”
android:layout_width=”match_parent”
android:layout_height=”?attr/actionBarSize”
android:background=”?attr/colorPrimary”
app:layout_scrollFlags=”scroll|enterAlways”
app:popupTheme=”@style/ThemeOverlay.AppCompat.Light”/>

<TextView
android:id=”@+id/id_tv_content”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_centerInParent=”true”
android:text=”HelloWorld”
android:textSize=”30sp”/>
</RelativeLayout>

<android.support.design.widget.NavigationView
android:id=”@+id/id_nv_menu”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:layout_gravity=”left”
android:fitsSystemWindows=”true”
app:headerLayout=”@layout/header_just_username”
app:menu=”@menu/menu_drawer”
/>

</android.support.v4.widget.DrawerLayout>

可以看到我们的*外层是DrawerLayout,里面一个content,一个作为drawer。我们的drawer为NavigationView。
注意这个view的两个属性app:headerLayout=”@layout/header_just_username”和app:menu=”@menu/menu_drawer”,分别代表drawer布局中的header和menuitem区域,当然你可以根据自己的情况使用。

接下来看看header的布局文件和menu配置文件:

<?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=”192dp”
android:background=”?attr/colorPrimaryDark”
android:orientation=”vertical”
android:padding=”16dp”
android:theme=”@style/ThemeOverlay.AppCompat.Dark”>

<TextView
android:id=”@+id/id_link”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignParentBottom=”true”
android:layout_marginBottom=”16dp”
android:text=”http://blog.csdn.net/lmj623565791″/>

<TextView
android:id=”@+id/id_username”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_above=”@id/id_link”
android:text=”Zhang Hongyang”/>

<ImageView
android:layout_width=”72dp”
android:layout_height=”72dp”
android:layout_above=”@id/id_username”
android:layout_marginBottom=”16dp”
android:src=”@mipmap/icon”/>

</RelativeLayout>

<?xml version=”1.0″ encoding=”utf-8″?>
<menu xmlns:android=”http://schemas.android.com/apk/res/android”>

<group android:checkableBehavior=”single”>
<item
android:id=”@+id/nav_home”
android:icon=”@drawable/ic_dashboard”
android:title=”Home”/>
<item
android:id=”@+id/nav_messages”
android:icon=”@drawable/ic_event”
android:title=”Messages”/>
<item
android:id=”@+id/nav_friends”
android:icon=”@drawable/ic_headset”
android:title=”Friends”/>
<item
android:id=”@+id/nav_discussion”
android:icon=”@drawable/ic_forum”
android:title=”Discussion”/>
</group>

<item android:title=”Sub items”>
<menu>
<item
android:icon=”@drawable/ic_dashboard”
android:title=”Sub item 1″/>
<item
android:icon=”@drawable/ic_forum”
android:title=”Sub item 2″/>
</menu>
</item>

</menu>

别放错文件夹哈~

布局文件写完了,基本就好了,是不是很爽~看似复杂的效果,写写布局文件就ok。

ps:默认的颜色很多是从当前的主题中提取的,比如icon的stateColor,当然你也可以通过以下属性修改部分样式:

app:itemIconTint=””
app:itemBackground=””
app:itemTextColor=””

(二)Activity
*后是Activity:

package com.imooc.testandroid;

import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;

public class NavigationViewActivity extends ActionBarActivity
{

private DrawerLayout mDrawerLayout;
private NavigationView mNavigationView;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_navigation_view);

mDrawerLayout = (DrawerLayout) findViewById(R.id.id_drawer_layout);
mNavigationView = (NavigationView) findViewById(R.id.id_nv_menu);

Toolbar toolbar = (Toolbar) findViewById(R.id.id_toolbar);
setSupportActionBar(toolbar);

final ActionBar ab = getSupportActionBar();
ab.setHomeAsUpIndicator(R.drawable.ic_menu);
ab.setDisplayHomeAsUpEnabled(true);

setupDrawerContent(mNavigationView);

}

private void setupDrawerContent(NavigationView navigationView)
{
navigationView.setNavigationItemSelectedListener(

new NavigationView.OnNavigationItemSelectedListener()
{

@Override
public boolean onNavigationItemSelected(MenuItem menuItem)
{
menuItem.setChecked(true);
mDrawerLayout.closeDrawers();
return true;
}
});
}

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_navigation_view, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item)
{
if(item.getItemId() == android.R.id.home)
{
mDrawerLayout.openDrawer(GravityCompat.START);
return true ;
}
return super.onOptionsItemSelected(item);
}

}

我们在Activity里面可以通过navigationView去navigationView.setNavigationItemSelectedListener,当selected的时候,menuItem去setChecked(true)。

别忘了设置theme~

<resources>

<!– Base application theme. –>
<style name=”AppTheme” parent=”Theme.AppCompat.Light.DarkActionBar”>
<!– Customize your theme here. –>
</style>

<style name=”Theme.DesignDemo” parent=”Base.Theme.DesignDemo”>
</style>

<style name=”Base.Theme.DesignDemo” parent=”Theme.AppCompat.Light.NoActionBar”>
<item name=”colorPrimary”>#673AB7</item>
<item name=”colorPrimaryDark”>#512DA8</item>
<item name=”colorAccent”>#FF4081</item>
<item name=”android:windowBackground”>@color/window_background</item>
</style>

</resources>

<color name=”window_background”>#FFF5F5F5</color>

<activity
android:name=”.NavigationViewActivity”
android:label=”@string/title_activity_navigation_view”
android:theme=”@style/Theme.DesignDemo”>
</activity>

ok,到此就搞定了~~

不过存在一个问题,此时你如果点击Sub items里面的Sub item,如果你期望当前选中应该是Sub item,你会发现不起作用。那怎么办呢?

(三)Sub Item支持Cheable
这里可以修改menu的配置文件:

<?xml version=”1.0″ encoding=”utf-8″?>
<menu xmlns:android=”http://schemas.android.com/apk/res/android”>

<group>
<item
android:id=”@+id/nav_home”
android:checkable=”true”
android:icon=”@drawable/ic_dashboard”
android:title=”Home”/>
<item
android:id=”@+id/nav_messages”
android:checkable=”true”
android:icon=”@drawable/ic_event”
android:title=”Messages”/>
<item
android:id=”@+id/nav_friends”
android:checkable=”true”
android:icon=”@drawable/ic_headset”
android:title=”Friends”/>
<item
android:id=”@+id/nav_discussion”
android:checkable=”true”
android:icon=”@drawable/ic_forum”
android:title=”Discussion”/>
</group>

<item android:title=”Sub items”>
<menu>
<item
android:checkable=”true”
android:icon=”@drawable/ic_dashboard”
android:title=”Sub item 1″/>
<item
android:checkable=”true”
android:icon=”@drawable/ic_forum”
android:title=”Sub item 2″/>
</menu>
</item>

</menu>

将android:checkableBehavior=”single”去掉,然后给每个item项添加android:checkable=”true”。

然后在代码中:

navigationView.setNavigationItemSelectedListener(

new NavigationView.OnNavigationItemSelectedListener()
{

private MenuItem mPreMenuItem;

@Override
public boolean onNavigationItemSelected(MenuItem menuItem)
{
if (mPreMenuItem != null) mPreMenuItem.setChecked(false);
menuItem.setChecked(true);
mDrawerLayout.closeDrawers();
mPreMenuItem = menuItem;
return true;
}
});

存一下preMenuItem,手动切换。

效果:%title插图%num

 

ok,哈~其实这个还是参考链接2里面的一个评论说的~~

到此用法就介绍完了有木有一点小激动~ 但是还有个问题,对于app,就像ActionBar*初的出现,一开始大家都欢欣鼓舞,后来发现app中多数情况下需要去定制,尼玛,是不是忽然觉得ActionBar太死板了,恶心死了(当然了现在有了ToolBar灵活度上好多了)对于上述NavigationView可能也会存在定制上的问题,比如我希望选中的Item左边有个高亮的竖线之类的效果。那么,针对于各种需求,想要能解决各种问题,*好的方式就是说对于NavigationView的效果自己可以实现。*好,我们就来看看NavigationView自己实现有多难?

三、自己实现NavigationView效果
其实NavigationView的实现非常简单,一个ListView就可以了,甚至都不需要去自定义,简单写一个Adapter就行了~~

%title插图%num

首先观察该图,有没有发现神奇之处,恩,你肯定发现不了,因为我们做的太像了。

其实这个图就是我通过ListView写的一个~是不是和原版很像(~哈~参考了源码的实现,当然像。)

接下来分析,如果说是ListView,那么Item的type肯定不止一种,那我们数一数种类:

带图标和文本的
仅仅是文本的 Sub Items
分割线
你会说还有顶部那个,顶部是headview呀~~

这么分析完成,是不是瞬间觉得没有难度了~

(一)首先布局文件
<?xml version=”1.0″ encoding=”utf-8″?>
<android.support.v4.widget.DrawerLayout
android:id=”@+id/id_drawer_layout”
xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:fitsSystemWindows=”true”
>

<RelativeLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<android.support.v7.widget.Toolbar
android:id=”@+id/id_toolbar”
android:layout_width=”match_parent”
android:layout_height=”?attr/actionBarSize”
android:background=”?attr/colorPrimary”
app:layout_scrollFlags=”scroll|enterAlways”
app:popupTheme=”@style/ThemeOverlay.AppCompat.Light”/>

<TextView
android:id=”@+id/id_tv_content”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_centerInParent=”true”
android:text=”HelloWorld”
android:textSize=”30sp”/>
</RelativeLayout>

<ListView
android:id=”@+id/id_lv_left_menu”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:layout_gravity=”start”
android:paddingTop=”0dp”
android:background=”#ffffffff”
android:clipToPadding=”false”
android:divider=”@null”
android:listSelector=”?attr/selectableItemBackground”
/>

</android.support.v4.widget.DrawerLayout>

布局文件上:和上文对比,我们仅仅把NavigationView换成了ListView.

下面注意了,一大波代码来袭.

(二) Activity
package com.imooc.testandroid;

public class NavListViewActivity extends ActionBarActivity
{
private ListView mLvLeftMenu;
private DrawerLayout mDrawerLayout;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nav_list_view);

mDrawerLayout = (DrawerLayout) findViewById(R.id.id_drawer_layout);
mLvLeftMenu = (ListView) findViewById(R.id.id_lv_left_menu);

Toolbar toolbar = (Toolbar) findViewById(R.id.id_toolbar);
setSupportActionBar(toolbar);

final ActionBar ab = getSupportActionBar();
ab.setHomeAsUpIndicator(R.drawable.ic_menu);
ab.setDisplayHomeAsUpEnabled(true);

setUpDrawer();
}

private void setUpDrawer()
{
LayoutInflater inflater = LayoutInflater.from(this);
mLvLeftMenu.addHeaderView(inflater.inflate(R.layout.header_just_username, mLvLeftMenu, false));
mLvLeftMenu.setAdapter(new MenuItemAdapter(this));
}

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_nav_list_view, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item)
{
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == android.R.id.home)
{
mDrawerLayout.openDrawer(GravityCompat.START);
return true;
}

return super.onOptionsItemSelected(item);
}

}

直接看onCreate中的setUpDrawer(),可以看到我们首先去addHeadView,然后去setAdapter。

那么核心代码就是我们的Adapter了~~

(三)MenuItemAdapter

public class LvMenuItem
{
public LvMenuItem(int icon, String name)
{
this.icon = icon;
this.name = name;

if (icon == NO_ICON && TextUtils.isEmpty(name))
{
type = TYPE_SEPARATOR;
} else if (icon == NO_ICON)
{
type = TYPE_NO_ICON;
} else
{
type = TYPE_NORMAL;
}

if (type != TYPE_SEPARATOR && TextUtils.isEmpty(name))
{
throw new IllegalArgumentException(“you need set a name for a non-SEPARATOR item”);
}

L.e(type + “”);

}

public LvMenuItem(String name)
{
this(NO_ICON, name);
}

public LvMenuItem()
{
this(null);
}

private static final int NO_ICON = 0;
public static final int TYPE_NORMAL = 0;
public static final int TYPE_NO_ICON = 1;
public static final int TYPE_SEPARATOR = 2;

int type;
String name;
int icon;

}

public class MenuItemAdapter extends BaseAdapter
{
private final int mIconSize;
private LayoutInflater mInflater;
private Context mContext;

public MenuItemAdapter(Context context)
{
mInflater = LayoutInflater.from(context);
mContext = context;

mIconSize = context.getResources().getDimensionPixelSize(R.dimen.drawer_icon_size);//24dp
}

private List<LvMenuItem> mItems = new ArrayList<LvMenuItem>(
Arrays.asList(
new LvMenuItem(R.drawable.ic_dashboard, “Home”),
new LvMenuItem(R.drawable.ic_event, “Messages”),
new LvMenuItem(R.drawable.ic_headset, “Friends”),
new LvMenuItem(R.drawable.ic_forum, “Discussion”),
new LvMenuItem(),
new LvMenuItem(“Sub Items”),
new LvMenuItem(R.drawable.ic_dashboard, “Sub Item 1”),
new LvMenuItem(R.drawable.ic_forum, “Sub Item 2″)
));

@Override
public int getCount()
{
return mItems.size();
}

@Override
public Object getItem(int position)
{
return mItems.get(position);
}

@Override
public long getItemId(int position)
{
return position;
}

@Override
public int getViewTypeCount()
{
return 3;
}

@Override
public int getItemViewType(int position)
{
return mItems.get(position).type;
}

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
LvMenuItem item = mItems.get(position);
switch (item.type)
{
case LvMenuItem.TYPE_NORMAL:
if (convertView == null)
{
convertView = mInflater.inflate(R.layout.design_drawer_item, parent,
false);
}
TextView itemView = (TextView) convertView;
itemView.setText(item.name);
Drawable icon = mContext.getResources().getDrawable(item.icon);
setIconColor(icon);
if (icon != null)
{
icon.setBounds(0, 0, mIconSize, mIconSize);
TextViewCompat.setCompoundDrawablesRelative(itemView, icon, null, null, null);
}

break;
case LvMenuItem.TYPE_NO_ICON:
if (convertView == null)
{
convertView = mInflater.inflate(R.layout.design_drawer_item_subheader,
parent, false);
}
TextView subHeader = (TextView) convertView;
subHeader.setText(item.name);
break;
case LvMenuItem.TYPE_SEPARATOR:
if (convertView == null)
{
convertView = mInflater.inflate(R.layout.design_drawer_item_separator,
parent, false);
}
break;
}

return convertView;
}

public void setIconColor(Drawable icon)
{
int textColorSecondary = android.R.attr.textColorSecondary;
TypedValue value = new TypedValue();
if (!mContext.getTheme().resolveAttribute(textColorSecondary, value, true))
{
return;
}
int baseColor = mContext.getResources().getColor(value.resourceId);
icon.setColorFilter(baseColor, PorterDuff.Mode.MULTIPLY);
}
}

首先我们的每个Item对应于一个LvMenuItem,包含icon、name、type,可以看到我们的type分为3类。

那么adapter中代码就很简单了,多Item布局的写法。

这样就完成了,我们自己去书写NavigationView,是不是很简单~~如果你的app需要类似效果,但是又与NavigationView的效果并非一模一样,可以考虑按照上面的思路自己写一个。

*后贴一下用到的Item的布局文件:

design_drawer_item_subheader.xml
<?xml version=”1.0″ encoding=”utf-8″?>
<TextView xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”?attr/listPreferredItemHeightSmall”
android:gravity=”center_vertical|start”
android:maxLines=”1″
android:paddingLeft=”?attr/listPreferredItemPaddingLeft”
android:paddingRight=”?attr/listPreferredItemPaddingRight”
android:textAppearance=”?attr/textAppearanceListItem”
android:textColor=”?android:textColorSecondary”/>

design_drawer_item_separator.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<FrameLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”>

<View android:layout_width=”match_parent”
android:layout_height=”1dp”
android:background=”?android:attr/listDivider”/>

</FrameLayout>

design_drawer_item,xml:
<?xml version=”1.0″ encoding=”utf-8″?>
<TextView
xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”?attr/listPreferredItemHeightSmall”
android:paddingLeft=”?attr/listPreferredItemPaddingLeft”
android:paddingRight=”?attr/listPreferredItemPaddingRight”
android:drawablePadding=”32dp”
android:gravity=”center_vertical|start”
android:maxLines=”1″
android:textAppearance=”?attr/textAppearanceListItem”
android:textColor=”?android:attr/textColorPrimary”/>

ok,其实上述ListView的写法也正是NavigationView的源码实现~~item的布局文件直接从源码中拖出来的,还是爽爽哒~

源码点击下载
~~hava a nice day ~~

TabHost详解

正文
TabHost的实现分为两种,一个是不继承TabActivity,一个是继承自TabActivity;当然了选用继承自TabActivity的话就相对容易一些,下面来看看分别是怎样来实现的吧。

方法一、定义tabhost:不用继承TabActivity
1、布局文件:activity_main.xml
<LinearLayout 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:orientation=”vertical”
tools:context=”.MainActivity” >
<Button
android:id=”@+id/button1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”Button” />
<TabHost
android:id=”@+id/tabhost”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”>

<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical” >

<TabWidget
android:id=”@android:id/tabs”
android:layout_width=”match_parent”
android:layout_height=”wrap_content” >
</TabWidget>

<FrameLayout
android:id=”@android:id/tabcontent”
android:layout_width=”match_parent”
android:layout_height=”match_parent” >

<!– *个tab的布局 –>
<LinearLayout
android:id=”@+id/tab1″
android:layout_width=”match_parent”
android:layout_height=”match_parent” >

<TextView
android:id=”@+id/textView1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”林炳东” />

</LinearLayout>

<!– 第二个tab的布局 –>
<LinearLayout
android:id=”@+id/tab2″
android:layout_width=”match_parent”
android:layout_height=”match_parent” >

<TextView
android:id=”@+id/textView2″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”张小媛” />

</LinearLayout>

<!– 第三个tab的布局 –>
<LinearLayout
android:id=”@+id/tab3″
android:layout_width=”match_parent”
android:layout_height=”match_parent” >

<TextView
android:id=”@+id/textView3″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”马贝贝” />

</LinearLayout>
</FrameLayout>
</LinearLayout>
</TabHost>

</LinearLayout>
2、JAVA代码

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TabHost th=(TabHost)findViewById(R.id.tabhost);
th.setup(); //初始化TabHost容器

//在TabHost创建标签,然后设置:标题/图标/标签页布局
th.addTab(th.newTabSpec(“tab1”).setIndicator(“标签1”,getResources().getDrawable(R.drawable.ic_launcher)).setContent(R.id.tab1));
th.addTab(th.newTabSpec(“tab2”).setIndicator(“标签2”,null).setContent(R.id.tab2));
th.addTab(th.newTabSpec(“tab3”).setIndicator(“标签3”,null).setContent(R.id.tab3));

//上面的null可以为getResources().getDrawable(R.drawable.图片名)设置图标

}
}
效果图:

%title插图%num

此例源码地址:http://download.csdn.net/detail/harvic880925/6657611  (不要分,欢迎下载)

方法二:Tab的内容分开:不用继承TabActivity
1、*个tab的XML布局文件,tab1.xml:
<?xml version=”1.0″ encoding=”UTF-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:id=”@+id/LinearLayout01″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”>
<TextView
android:text=”我是标签1的内容喔”
android:id=”@+id/TextView01″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”>
</TextView>
</LinearLayout>
2、第二个tab的XML布局文件,tab2.xml:
<?xml version=”1.0″ encoding=”UTF-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:id=”@+id/LinearLayout02″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”>

<TextView android:text=”标签2″
android:id=”@+id/TextView01″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</LinearLayout>
3、主布局文件,activity_main.xml:
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:orientation=”vertical” >

<TabHost
android:id=”@+id/tabhost”
android:layout_width=”match_parent”
android:layout_height=”match_parent” >

<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical” >

<TabWidget
android:id=”@android:id/tabs”
android:layout_width=”match_parent”
android:layout_height=”wrap_content” >
</TabWidget>

<FrameLayout
android:id=”@android:id/tabcontent”
android:layout_width=”match_parent”
android:layout_height=”match_parent” >

</FrameLayout>
</LinearLayout>
</TabHost>

</LinearLayout>
4、JAVA代码:
public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TabHost m = (TabHost)findViewById(R.id.tabhost);
m.setup();

LayoutInflater i=LayoutInflater.from(this);
i.inflate(R.layout.tab1, m.getTabContentView());
i.inflate(R.layout.tab2, m.getTabContentView());//动态载入XML,而不需要Activity

m.addTab(m.newTabSpec(“tab1”).setIndicator(“标签1”).setContent(R.id.LinearLayout01));
m.addTab(m.newTabSpec(“tab2”).setIndicator(“标签2”).setContent(R.id.LinearLayout02));

}
}
效果图:

%title插图%num
此例源码地址:http://download.csdn.net/detail/harvic880925/6657679   (不要分,欢迎下载)

方法三:继承自TabActivity
1、主布局文件,activity_main.xml:
<?xml version=”1.0″ encoding=”utf-8″?>
<FrameLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”>

<!– *个布局 –>
<LinearLayout
android:id=”@+id/view1″
android:layout_width=”match_parent”
android:layout_height=”match_parent” >
<TextView
android:id=”@+id/textView1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”张小媛” />
</LinearLayout>

<!– 第二个布局 –>
<LinearLayout
android:id=”@+id/view2″
android:layout_width=”match_parent”
android:layout_height=”match_parent” >

<TextView
android:id=”@+id/textView2″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”马贝贝” />
</LinearLayout>

<!– 第三个布局 –>
<TextView android:id=”@+id/view3″
android:background=”#00ff00″
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:text=”Tab3″/>

</FrameLayout>
2、JAVA代码:
先将派生自Activity改为TabActivity,然后代码如下:

 

public class MainActivity extends TabActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(“TabDemoActivity”);
TabHost tabHost = getTabHost();
LayoutInflater.from(this).inflate(R.layout.activity_main,
tabHost.getTabContentView(), true);
tabHost.addTab(tabHost.newTabSpec(“tab1”).setIndicator(“tab1”, getResources().getDrawable(R.drawable.ic_launcher))
.setContent(R.id.view1));
tabHost.addTab(tabHost.newTabSpec(“tab3”).setIndicator(“tab2”)
.setContent(R.id.view2));
tabHost.addTab(tabHost.newTabSpec(“tab3”).setIndicator(“tab3”)
.setContent(R.id.view3));

//标签切换事件处理,setOnTabChangedListener
tabHost.setOnTabChangedListener(new OnTabChangeListener(){
@Override
public void onTabChanged(String tabId) {
if (tabId.equals(“tab1”)) { //*个标签
}
if (tabId.equals(“tab2”)) { //第二个标签
}
if (tabId.equals(“tab3”)) { //第三个标签
}
}
});

}

}
效果如下:
%title插图%num

TabHost的两种用法

方法1.在tabhost中直接导入xml进去。如下:

  1. <span style=“font-size:18px;”> <TabHost
  2. android:id=“@+id/tabhost”
  3. android:layout_width=“fill_parent”
  4. android:layout_height=“fill_parent” >
  5. <LinearLayout
  6. android:layout_width=“fill_parent”
  7. android:layout_height=“fill_parent”
  8. android:orientation=“vertical” >
  9. <TabWidget
  10. android:id=“@android:id/tabs”
  11. android:layout_width=“fill_parent”
  12. android:layout_height=“55dp”
  13. android:gravity=“center” />
  14. <FrameLayout
  15. android:id=“@android:id/tabcontent”
  16. android:layout_width=“fill_parent”
  17. android:layout_height=“fill_parent” >
  18. <ScrollView
  19. android:layout_width=“match_parent”
  20. android:layout_height=“wrap_content” >
  21. <RelativeLayout
  22. android:id=“@+id/relativeLayout_weight”
  23. android:layout_width=“match_parent”
  24. android:layout_height=“wrap_content” >
  25. <include
  26. android:id=“@+id/layout_add_data_weight”
  27. android:layout_width=“match_parent”
  28. android:layout_height=“wrap_content”
  29. layout=“@layout/view_xdata_weight” />
  30. </RelativeLayout>
  31. </ScrollView>
  32. </FrameLayout>
  33. </LinearLayout>
  34. </TabHost>
  35. </span>

 

  1. <span style=“font-size:18px;”>mTabHost = (TabHost) this.findViewById(R.id.tabhost);
  2. mTabHost.setup();
  3. TabHost.TabSpec tabWeight = mTabHost.newTabSpec(“tab_weight”);
  4. tabWeight.setContent(R.id.relativeLayout_weight);
  5. tabWeight.setIndicator(getResources().getString(R.string.weight));
  6. mTabHost.addTab(tabWeight);</span>

第二种方法就是和Activity关联在一起:

使用方式如下 :

  1. <span style=“font-size:18px;”> TabHost tabHost = getTabHost(); // The activity TabHost
  2. TabHost.TabSpec spec; // Resusable TabSpec for each tab
  3. Intent intent; // Reusable Intent for each tab
  4. intent = new Intent().setClass(this, ArtistsActivity.class);
  5. spec = tabHost.newTabSpec(“artists”).setIndicator(“Artists”,
  6. res.getDrawable(R.drawable.icon)).setContent(intent);
  7. System.out.println(spec);
  8. tabHost.addTab(spec);</span>

Android选项卡TabHost功能和用法

1、选项卡TabHost介绍
TabHost可以方便地在窗口上放置多个标签页,每个标签页相当于获得了一个与外部容器大小相同的组件摆放区域
TabHost是一个简单的容器,提供如下两种方法来创建选项卡
newTabSpec(String tag):创建选项卡
addTab(TabHost.TabSpec tabSpec):添加选项卡
使用TabHost有三种方法

2、方法1,继承TabActivity
主布局文件不需要定义TabHost组件,这里用三个垂直的LinearLayout作为标签页,每个标签页里面有两个TextView组件,main_tab.xml代码如下:

  1. <FrameLayout
  2. xmlns:android=“http://schemas.android.com/apk/res/android”
  3. android:id=“@android:id/tabcontent”
  4. android:layout_width=“match_parent”
  5. android:layout_height=“match_parent”>
  6. <!– 定义*个标签页的内容 –>
  7. <LinearLayout
  8. android:id=“@+id/tab01”
  9. android:orientation=“vertical”
  10. android:layout_width=“match_parent”
  11. android:layout_height=“match_parent”>
  12. <TextView
  13. android:layout_width=“wrap_content”
  14. android:layout_height=“wrap_content”
  15. android:text=“*个标签页的第1个TextView组件”
  16. android:textSize=“8pt” />
  17. <TextView
  18. android:layout_width=“wrap_content”
  19. android:layout_height=“wrap_content”
  20. android:text=“*个标签页的第2个TextView组件”
  21. android:textSize=“8pt” />
  22. </LinearLayout>
  23. <!– 定义第二个标签页的内容 –>
  24. <LinearLayout
  25. android:id=“@+id/tab02”
  26. android:orientation=“vertical”
  27. android:layout_width=“match_parent”
  28. android:layout_height=“match_parent”>
  29. <TextView
  30. android:layout_width=“wrap_content”
  31. android:layout_height=“wrap_content”
  32. android:text=“第二个标签页的第1个TextView组件”
  33. android:textSize=“8pt” />
  34. <TextView
  35. android:layout_width=“wrap_content”
  36. android:layout_height=“wrap_content”
  37. android:text=“第二个标签页的第2个TextView组件”
  38. android:textSize=“8pt” />
  39. </LinearLayout>
  40. <!– 定义第三个标签页的内容 –>
  41. <LinearLayout
  42. android:id=“@+id/tab03”
  43. android:orientation=“vertical”
  44. android:layout_width=“match_parent”
  45. android:layout_height=“match_parent”>
  46. <TextView
  47. android:layout_width=“wrap_content”
  48. android:layout_height=“wrap_content”
  49. android:text=“第三个标签页的第1个TextView组件”
  50. android:textSize=“8pt” />
  51. <TextView
  52. android:layout_width=“wrap_content”
  53. android:layout_height=“wrap_content”
  54. android:text=“第三个标签页的第2个TextView组件”
  55. android:textSize=“8pt” />
  56. </LinearLayout>
  57. </FrameLayout>

在Java代码中将Activity继承TabActivity,具体代码如下:

  1. package com.example.tabhosttest;
  2. import android.app.TabActivity;
  3. import android.os.Bundle;
  4. import android.view.LayoutInflater;
  5. import android.widget.TabHost;
  6. import android.widget.TabHost.OnTabChangeListener;
  7. import android.widget.Toast;
  8. public class MainActivity extends TabActivity
  9. {
  10. @Override
  11. public void onCreate(Bundle savedInstanceState)
  12. {
  13. super.onCreate(savedInstanceState);
  14. //setContentView(R.layout.main_tab);
  15. // 获取该Activity里面的TabHost组件
  16. TabHost tabHost = getTabHost();
  17. LayoutInflater.from(this).inflate(R.layout.main_tab,
  18. tabHost.getTabContentView(), true);
  19. // 创建*个Tab页
  20. /*TabHost.TabSpec tab1 = tabHost.newTabSpec("tab1")
  21. .setIndicator("标签页一") // 设置标题
  22. .setContent(R.id.tab01); //设置内容
  23. // 添加*个标签页
  24. tabHost.addTab(tab1);
  25. TabHost.TabSpec tab2 = tabHost.newTabSpec("tab2")
  26. .setIndicator("标签页二")
  27. .setContent(R.id.tab02);
  28. // 添加第二个标签页
  29. tabHost.addTab(tab2);
  30. TabHost.TabSpec tab3 = tabHost.newTabSpec("tab3")
  31. .setIndicator("标签页三")
  32. .setContent(R.id.tab03);
  33. // 添加第三个标签页
  34. tabHost.addTab(tab3);*/
  35. /* 以上创建和添加标签页也可以用如下代码实现 */
  36. tabHost.addTab(tabHost.newTabSpec("tab1").setIndicator("标签页一").setContent(R.id.tab01));
  37. tabHost.addTab(tabHost.newTabSpec("tab2").setIndicator("标签页二").setContent(R.id.tab02));
  38. tabHost.addTab(tabHost.newTabSpec("tab3").setIndicator("标签页三").setContent(R.id.tab03));
  39. //标签切换事件处理,setOnTabChangedListener
  40. tabHost.setOnTabChangedListener(new OnTabChangeListener(){
  41. @Override
  42. // tabId是newTabSpec参数设置的tab页名,并不是layout里面的标识符id
  43. public void onTabChanged(String tabId) {
  44. if (tabId.equals("tab1")) { //*个标签
  45. Toast.makeText(MainActivity.this, "点击标签页一", Toast.LENGTH_SHORT).show();
  46. }
  47. if (tabId.equals("tab2")) { //第二个标签
  48. Toast.makeText(MainActivity.this, "点击标签页二", Toast.LENGTH_SHORT).show();
  49. }
  50. if (tabId.equals("tab3")) { //第三个标签
  51. Toast.makeText(MainActivity.this, "点击标签页三", Toast.LENGTH_SHORT).show();
  52. }
  53. }
  54. });
  55. }
  56. }

这里有两个地方注意一下,*没有使用setContentView,而是用inflate方法;第二在设置标签页改变监听器时,onTabChanged的参数tabId是使用newTabSpec方法的参数设置的标签页名称,而不是布局文件中标签页的标识符Id;第三为了测试方便使用了Toast,
关于inflate和Toast,下面再学习,先简单了解一下
LayoutInflater的作用是将xml布局文件实例话,Toast是在屏幕上显示提示信息
点击第二个标签页之后,显示效果如下图9-2-1所示:

%title插图%num

图 9-2-1

 

3、方法2,在布局文件中使用TabHost,不用继承TabActivity
使用findViewById获取TabHost组件
1 在界面布局中定义TabHost组件,并为该组件定义选项卡内容
2 使用findViewById获取TabHost组件
3 通过TabHost对象的方法来创建和添加选项卡
布局文件为activity_main.xml如下:

  1. <TabHost
  2.     xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:id=“@android:id/tabhost”
  4.      android:layout_width=“match_parent”
  5.     android:layout_height=“match_parent”
  6.     android:layout_weight=“1”>
  7.     <LinearLayout 
  8.         android:orientation=“vertical”
  9.         android:layout_width=“match_parent”
  10.         android:layout_height=“match_parent”>
  11.        <!– TabWidget组件id值不可变–>
  12.        <TabWidget 
  13.            android:id=“@android:id/tabs”
  14.             android:layout_width=“match_parent”
  15.             android:layout_height=“wrap_content”>
  16.        </TabWidget>
  17.        <!– FrameLayout布局,id值不可变–>
  18.        <FrameLayout 
  19.            android:id=“@android:id/tabcontent”
  20.            android:layout_width=“fill_parent”
  21.            android:layout_height=“fill_parent”
  22.            android:layout_above=“@android:id/tabs”>
  23.            <!– *个tab的布局 –>
  24.            <LinearLayout  
  25.                android:id=“@+id/tab1”
  26.                android:layout_width=“match_parent”
  27.                android:layout_height=“match_parent” >
  28.                <TextView  
  29.                     android:layout_width=“wrap_content”
  30.                     android:layout_height=“wrap_content”
  31.                     android:text=“*个tab的布局” />
  32.            </LinearLayout>
  33.            <!– 第二个tab的布局 –>
  34.            <LinearLayout  
  35.                android:id=“@+id/tab2”
  36.                android:layout_width=“match_parent”
  37.                android:layout_height=“match_parent” >
  38.                <TextView  
  39.                     android:layout_width=“wrap_content”
  40.                     android:layout_height=“wrap_content”
  41.                     android:text=“第二个tab的布局” />
  42.            </LinearLayout>
  43.        </FrameLayout>
  44.     </LinearLayout>
  45. </TabHost>

 

java代码如下:

package com.example.tabhost;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TabHost;

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

TabHost tab = (TabHost) findViewById(android.R.id.tabhost);

//初始化TabHost容器
tab.setup();
//在TabHost创建标签,然后设置:标题/图标/标签页布局
tab.addTab(tab.newTabSpec(“tab1”).setIndicator(“本地音乐” , null).setContent(R.id.tab1));
tab.addTab(tab.newTabSpec(“tab2”).setIndicator(“网络音乐” , null).setContent(R.id.tab2));

}

}

这段非转载代码,用的和作者不是一个xml,效果图如下:

%title插图%num

4、方法3基本和方法2类似,不用继承TabActivity,只是Tab的内容分开到单独的xml文件,每个标签页都需要inflate一次,和方法2*大的区别就是标签页分开到不同的xml文件中
tab1对应的tab1.xml:

  1. <LinearLayout
  2. xmlns:android=“http://schemas.android.com/apk/res/android”
  3. android:id=“@+id/tab01”
  4. android:orientation=“vertical”
  5. android:layout_width=“match_parent”
  6. android:layout_height=“match_parent”>
  7. <TextView
  8. android:layout_width=“wrap_content”
  9. android:layout_height=“wrap_content”
  10. android:text=“*个标签页的第1个TextView组件”
  11. android:textSize=“8pt” />
  12. <TextView
  13. android:layout_width=“wrap_content”
  14. android:layout_height=“wrap_content”
  15. android:text=“*个标签页的第2个TextView组件”
  16. android:textSize=“8pt” />
  17. </LinearLayout>

tab2对应的tab2.xml:

  1. <LinearLayout
  2. xmlns:android=“http://schemas.android.com/apk/res/android”
  3. android:id=“@+id/tab02”
  4. android:orientation=“vertical”
  5. android:layout_width=“match_parent”
  6. android:layout_height=“match_parent”>
  7. <TextView
  8. android:layout_width=“wrap_content”
  9. android:layout_height=“wrap_content”
  10. android:text=“第二个标签页的第1个TextView组件”
  11. android:textSize=“8pt” />
  12. <TextView
  13. android:layout_width=“wrap_content”
  14. android:layout_height=“wrap_content”
  15. android:text=“第二个标签页的第2个TextView组件”
  16. android:textSize=“8pt” />
  17. </LinearLayout>

tab3对应的tab3.xml:

  1. <LinearLayout
  2. xmlns:android=“http://schemas.android.com/apk/res/android”
  3. android:id=“@+id/tab03”
  4. android:orientation=“vertical”
  5. android:layout_width=“match_parent”
  6. android:layout_height=“match_parent”>
  7. <TextView
  8. android:layout_width=“wrap_content”
  9. android:layout_height=“wrap_content”
  10. android:text=“第三个标签页的第1个TextView组件”
  11. android:textSize=“8pt” />
  12. <TextView
  13. android:layout_width=“wrap_content”
  14. android:layout_height=“wrap_content”
  15. android:text=“第三个标签页的第2个TextView组件”
  16. android:textSize=“8pt” />
  17. </LinearLayout>

主布局文件main.xml:

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <TabHost
  3. xmlns:android=“http://schemas.android.com/apk/res/android”
  4. android:id=“@+id/tabhost”
  5. android:layout_width=“match_parent”
  6. android:layout_height=“match_parent”
  7. android:layout_weight=“1”>
  8. <LinearLayout
  9. android:layout_width=“match_parent”
  10. android:layout_height=“match_parent”
  11. android:orientation=“vertical”>
  12. <TabWidget
  13. android:id=“@android:id/tabs”
  14. android:layout_width=“match_parent”
  15. android:layout_height=“wrap_content”/>
  16. <FrameLayout
  17. android:id=“@android:id/tabcontent”
  18. android:layout_width=“match_parent”
  19. android:layout_height=“match_parent”>
  20. </FrameLayout>
  21. </LinearLayout>
  22. </TabHost>

Java代码如下:

  1. package com.example.tabhosttest;
  2. import android.app.Activity;
  3. import android.app.TabActivity;
  4. import android.os.Bundle;
  5. import android.view.LayoutInflater;
  6. import android.widget.TabHost;
  7. import android.widget.TabHost.OnTabChangeListener;
  8. import android.widget.Toast;
  9. public class MainActivity extends Activity
  10. {
  11. @Override
  12. public void onCreate(Bundle savedInstanceState)
  13. {
  14. super.onCreate(savedInstanceState);
  15. // 方法2、3使用:
  16. setContentView(R.layout.main);
  17. // 获取该Activity里面的TabHost组件
  18. // 方法1使用:
  19. // TabHost tabHost = getTabHost();
  20. // 方法1使用:
  21. // LayoutInflater.from(this).inflate(R.layout.main_tab, tabHost.getTabContentView(), true);
  22. // 方法2、3使用
  23. TabHost tabHost = (TabHost)findViewById(R.id.tabhost);
  24. tabHost.setup();
  25. // 方法3使用,动态载入xml,不需要Activity
  26. LayoutInflater.from(this).inflate(R.layout.tab1, tabHost.getTabContentView());
  27. LayoutInflater.from(this).inflate(R.layout.tab2, tabHost.getTabContentView());
  28. LayoutInflater.from(this).inflate(R.layout.tab3, tabHost.getTabContentView());
  29. // 创建*个Tab页
  30. /*TabHost.TabSpec tab1 = tabHost.newTabSpec("tab1")
  31. .setIndicator("标签页一") // 设置标题
  32. .setContent(R.id.tab01); //设置内容
  33. // 添加*个标签页
  34. tabHost.addTab(tab1);
  35. TabHost.TabSpec tab2 = tabHost.newTabSpec("tab2")
  36. .setIndicator("标签页二")
  37. .setContent(R.id.tab02);
  38. // 添加第二个标签页
  39. tabHost.addTab(tab2);
  40. TabHost.TabSpec tab3 = tabHost.newTabSpec("tab3")
  41. .setIndicator("标签页三")
  42. .setContent(R.id.tab03);
  43. // 添加第三个标签页
  44. tabHost.addTab(tab3);*/
  45. /* 以上创建和添加标签页也可以用如下代码实现 */
  46. tabHost.addTab(tabHost.newTabSpec("tab1").setIndicator("标签页一").setContent(R.id.tab01));
  47. tabHost.addTab(tabHost.newTabSpec("tab2").setIndicator("标签页二").setContent(R.id.tab02));
  48. tabHost.addTab(tabHost.newTabSpec("tab3").setIndicator("标签页三").setContent(R.id.tab03));
  49. //标签切换事件处理,setOnTabChangedListener
  50. tabHost.setOnTabChangedListener(new OnTabChangeListener(){
  51. @Override
  52. // tabId是newTabSpec*个参数设置的tab页名,并不是layout里面的标识符id
  53. public void onTabChanged(String tabId) {
  54. if (tabId.equals("tab1")) { //*个标签
  55. Toast.makeText(MainActivity.this, "点击标签页一", Toast.LENGTH_SHORT).show();
  56. }
  57. if (tabId.equals("tab2")) { //第二个标签
  58. Toast.makeText(MainActivity.this, "点击标签页二", Toast.LENGTH_SHORT).show();
  59. }
  60. if (tabId.equals("tab3")) { //第三个标签
  61. Toast.makeText(MainActivity.this, "点击标签页三", Toast.LENGTH_SHORT).show();
  62. }
  63. }
  64. });
  65. }
  66. }

 

Android Binder IPC分析

1.binder通信概述

binder通信是一种client-server的通信结构,
1.从表面上来看,是client通过获得一个server的代理接口,对server进行直接调用;
2.实际上,代理接口中定义的方法与server中定义的方法是一一对应的;
3.client调用某个代理接口中的方法时,代理接口的方法会将client传递的参数打包成为Parcel对象;
4.代理接口将该Parcel发送给内核中的binder driver.
5.server会读取binder driver中的请求数据,如果是发送给自己的,解包Parcel对象,处理并将结果返回;
6.整个的调用过程是一个同步过程,在server处理的时候,client会block住。

 

%title插图%num

2.service manager

Service Manager是一个linux级的进程,顾名思义,就是service的管理器。这里的service是什么概念呢?这里的service的概念和init过程中init.rc中的service是不同,init.rc中的service是都是linux进程,但是这里的service它并不一定是一个进程,也就是说可能一个或多个service属于同一个linux进程。在这篇文章中不加特殊说明均指android native端的service。

任何service在被使用之前,均要向SM(Service Manager)注册,同时客户端需要访问某个service时,应该首先向SM查询是否存在该服务。如果SM存在这个service,那么会将该service的handle返回给client,handle是每个service的唯一标识符。

SM的入口函数在service_manager.c中,下面是SM的代码部分
int main(int argc, char **argv)
{
struct binder_state *bs;
void *svcmgr = BINDER_SERVICE_MANAGER;

bs = binder_open(128*1024);

if (binder_become_context_manager(bs)) {
LOGE(“cannot become context manager (%s)/n”, strerror(errno));
return -1;
}

svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}

这个进程的主要工作如下:
1.初始化binder,打开/dev/binder设备;在内存中为binder映射128K字节空间;
2.指定SM对应的代理binder的handle为0,当client尝试与SM通信时,需要创建一个handle为0的代理binder,这里的代理binder其实就是*节中描述的那个代理接口;

3.通知binder driver(BD)使SM成为BD的context manager;
4.维护一个死循环,在这个死循环中,不停地去读内核中binder driver,查看是否有可读的内容;即是否有对service的操作要求, 如果有,则调用svcmgr_handler回调来处理请求的操作。

5.SM维护了一个svclist列表来存储service的信息。

%title插图%num

这里需要声明一下,当service在向SM注册时,该service就是一个client,而SM则作为了server。而某个进程需要与service通信时,此时这个进程为client,service才作为server。因此service不一定为server,有时它也是作为client存在的。

由于下面几节会介绍一些与binder通信相关的几个概念,所以将SM的功能介绍放在了后面的部分来讲。

应用和service之间的通信会涉及到2次binder通信。

1.应用向SM查询service是否存在,如果存在获得该service的代理binder,此为一次binder通信;
2.应用通过代理binder调用service的方法,此为第二次binder通信。

3.ProcessState

ProcessState是以单例模式设计的。每个进程在使用binder机制通信时,均需要维护一个ProcessState实例来描述当前进程在binder通信时的binder状态。
ProcessState有如下2个主要功能:
1.创建一个thread,该线程负责与内核中的binder模块进行通信,称该线程为Pool thread;
2.为指定的handle创建一个BpBinder对象,并管理该进程中所有的BpBinder对象。

3.1 Pool thread

在Binder IPC中,所有进程均会启动一个thread来负责与BD来直接通信,也就是不停的读写BD,这个线程的实现主体是一个IPCThreadState对象,下面会介绍这个类型。

下面是 Pool thread的启动方式:

ProcessState::self()->startThreadPool();

3.2 BpBinder获取

BpBinder主要功能是负责client向BD发送调用请求的数据。它是client端binder通信的核心对象,通过调用transact函数向BD发送调用请求的数据,它的构造函数如下:

BpBinder(int32_t handle);
通过BpBinder的构造函数发现,BpBinder会将当前通信中server的handle记录下来,当有数据发送时,会通知BD数据的发送目标。

ProcessState通过如下方式来获取BpBinder对象:

ProcessState::self()->getContextObject(handle);

在这个过程中,ProcessState会维护一个BpBinder的vector mHandleToObject,每当ProcessState创建一个BpBinder的实例时,回去查询mHandleToObject,如果对应的handle已经有binder指针,那么不再创建,否则创建binder并插入到mHandleToObject中。
ProcessState创建的BpBinder实例,一般情况下会作为参数构建一个client端的代理接口,这个代理接口的形式为BpINTERFACE,例如在与SM通信时,client会创建一个代理接口BpServiceManager.

4.IPCThreadState

IPCThreadState也是以单例模式设计的。由于每个进程只维护了一个ProcessState实例,同时ProcessState只启动一个Pool thread,也就是说每一个进程只会启动一个Pool thread,因此每个进程则只需要一个IPCThreadState即可。
Pool thread的实际内容则为:
IPCThreadState::self()->joinThreadPool();

 

ProcessState中有2个Parcel成员,mIn和mOut,Pool thread会不停的查询BD中是否有数据可读,如果有将其读出并保存到mIn,同时不停的检查mOut是否有数据需要向BD发送,如果有,则将其内容写入到BD中,总而言之,从BD中读出的数据保存到mIn,待写入到BD中的数据保存在了mOut中。

ProcessState中生成的BpBinder实例通过调用IPCThreadState的transact函数来向mOut中写入数据,这样的话这个binder IPC过程的client端的调用请求的发送过程就明了了。

 

IPCThreadState有两个重要的函数,talkWithDriver函数负责从BD读写数据,executeCommand函数负责解析并执行mIn中的数据。

%title插图%num

5.主要基类

5.1基类IInterface

为server端提供接口,它的子类声明了service能够实现的所有的方法;

5.2基类IBinder
BBinder与BpBinder均为IBinder的子类,因此可以看出IBinder定义了binder IPC的通信协议,BBinder与BpBinder在这个协议框架内进行的收和发操作,构建了基本的binder IPC机制。
5.3基类BpRefBase
client端在查询SM获得所需的的BpBinder后,BpRefBase负责管理当前获得的BpBinder实例。

 

 

6.两个接口类

6.1 BpINTERFACE

如果client想要使用binder IPC来通信,那么首先会从SM出查询并获得server端service的BpBinder,在client端,这个对象被认为是server端的远程代理。为了能够使client能够想本地调用一样调用一个远程server,server端需要向client提供一个接口,client在在这个接口的基础上创建一个BpINTERFACE,使用这个对象,client的应用能够想本地调用一样直接调用server端的方法。而不用去关心具体的binder IPC实现。
下面看一下BpINTERFACE的原型:
class BpINTERFACE : public BpInterface<IINTERFACE>

顺着继承关系再往上看
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase

BpINTERFACE分别继承自INTERFACE,和BpRefBase;
● BpINTERFACE既实现了service中各方法的本地操作,将每个方法的参数以Parcel的形式发送给BD。
例如BpServiceManager的
virtual status_t addService(const String16& name, const sp<IBinder>& service)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}
● 同时又将BpBinder作为了自己的成员来管理,将BpBinder存储在mRemote中,BpServiceManager通过调用BpRefBase的remote()来获得BpBinder指针。

 

6.2 BnINTERFACE

在定义android native端的service时,每个service均继承自BnINTERFACE(INTERFACE为service name)。BnINTERFACE类型定义了一个onTransact函数,这个函数负责解包收到的Parcel并执行client端的请求的方法。

顺着BnINTERFACE的继承关系再往上看,
class BnINTERFACE: public BnInterface<IINTERFACE>

IINTERFACE为client端的代理接口BpINTERFACE和server端的BnINTERFACE的共同接口类,这个共同接口类的目的就是保证service方法在C-S两端的一致性。

再往上看
class BnInterface : public INTERFACE, public BBinder

同时我们发现了BBinder类型,这个类型又是干什么用的呢?既然每个service均可视为一个binder,那么真正的server端的binder的操作及状态的维护就是通过继承自BBinder来实现的。可见BBinder是service作为binder的本质所在。

那么BBinder与BpBinder的区别又是什么呢?

其实它们的区别很简单,BpBinder是client端创建的用于消息发送的代理,而BBinder是server端用于接收消息的通道。查看各自的代码就会发现,虽然两个类型均有transact的方法,但是两者的作用不同,BpBinder的transact方法是向IPCThreadState实例发送消息,通知其有消息要发送给BD;而BBinder则是当IPCThreadState实例收到BD消息时,通过BBinder的transact的方法将其传递给它的子类BnSERVICE的onTransact函数执行server端的操作。

 

7. Parcel

Parcel是binder IPC中的*基本的通信单元,它存储C-S间函数调用的参数.但是Parcel只能存储基本的数据类型,如果是复杂的数据类型的话,在存储时,需要将其拆分为基本的数据类型来存储。

简单的Parcel读写不再介绍,下面着重介绍一下2个函数

 

7.1 writeStrongBinder

当client需要将一个binder向server发送时,可以调用此函数。例如
virtual status_t addService(const String16& name, const sp<IBinder>& service)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}

看一下writeStrongBinder的实体
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}

接着往里看flatten_binder
status_t flatten_binder(const sp<ProcessState>& proc,
const sp<IBinder>& binder, Parcel* out)
{
flat_binder_object obj;

obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
if (binder != NULL) {
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
if (proxy == NULL) {
LOGE(“null proxy”);
}
const int32_t handle = proxy ? proxy->handle() : 0;
obj.type = BINDER_TYPE_HANDLE;
obj.handle = handle;
obj.cookie = NULL;
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = local->getWeakRefs();
obj.cookie = local;
}
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = NULL;
obj.cookie = NULL;
}

return finish_flatten_binder(binder, obj, out);
}

还是拿addService为例,它的参数为一个BnINTERFACE类型指针,BnINTERFACE又继承自BBinder,
BBinder* BBinder::localBinder()
{
return this;
}
所以写入到Parcel的binder类型为BINDER_TYPE_BINDER,同时你在阅读SM的代码时会发现如果SM收到的service的binder类型不为BINDER_TYPE_HANDLE时,SM将不会将此service添加到svclist,但是很显然每个service的添加都是成功的,addService在开始传递的binder类型为BINDER_TYPE_BINDER,SM收到的binder类型为BINDER_TYPE_HANDLE,那么这个过程当中究竟发生了什么?
为了搞明白这个问题,花费我很多的事件,*终发现了问题的所在,原来在BD中做了如下操作(drivers/staging/android/Binder.c):

static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
……………………………………

if (fp->type == BINDER_TYPE_BINDER)
fp->type = BINDER_TYPE_HANDLE;
else
fp->type = BINDER_TYPE_WEAK_HANDLE;
fp->handle = ref->desc;
……………………………………
}

 

阅读完addService的代码,你会发现SM只是保存了service binder的handle和service的name,那么当client需要和某个service通信了,如何获得service的binder呢?看下一个函数

7.2 readStrongBinder

当server端收到client的调用请求之后,如果需要返回一个binder时,可以向BD发送这个binder,当IPCThreadState实例收到这个返回的Parcel时,client可以通过这个函数将这个被server返回的binder读出。

sp<IBinder> Parcel::readStrongBinder() const
{
sp<IBinder> val;
unflatten_binder(ProcessState::self(), *this, &val);
return val;
}

往里查看unflatten_binder

status_t unflatten_binder(const sp<ProcessState>& proc,
const Parcel& in, sp<IBinder>* out)
{
const flat_binder_object* flat = in.readObject(false);

if (flat) {
switch (flat->type) {
case BINDER_TYPE_BINDER:
*out = static_cast<IBinder*>(flat->cookie);
return finish_unflatten_binder(NULL, *flat, in);
case BINDER_TYPE_HANDLE:
*out = proc->getStrongProxyForHandle(flat->handle);
return finish_unflatten_binder(
static_cast<BpBinder*>(out->get()), *flat, in);
}
}
return BAD_TYPE;
}

发现如果server返回的binder类型为BINDER_TYPE_BINDER的话,也就是返回一个binder引用的话,直接获取这个binder;如果server返回的binder类型为BINDER_TYPE_HANDLE时,也就是server返回的仅仅是binder的handle,那么需要重新创建一个BpBinder返回给client。

有上面的代码可以看出,SM保存的service的binder仅仅是一个handle,而client则是通过向SM获得这个handle,从而重新构建代理binder与server通信。

这里顺带提一下一种特殊的情况,binder通信的双方即可作为client,也可以作为server.也就是说此时的binder通信是一个半双工的通信。那么在这种情况下,操作的过程会比单工的情况复杂,但是基本的原理是一样的,有兴趣可以分析一下MediaPlayer和MediaPlayerService的例子。

 

8. 经典桥段分析

main_ mediaserver.cpp
int main(int argc, char** argv)
{

//创建进程mediaserver的ProcessState实例
sp<ProcessState> proc(ProcessState::self());

//获得SM的BpServiceManager
sp<IServiceManager> sm = defaultServiceManager();
LOGI(“ServiceManager: %p”, sm.get());

//添加mediaserver中支持的service。
AudioFlinger::instantiate();
MediaPlayerService::instantiate();
CameraService::instantiate();
AudioPolicyService::instantiate();

//启动ProcessState的pool thread
ProcessState::self()->startThreadPool();

//这一步有重复之嫌,加不加无关紧要。
IPCThreadState::self()->joinThreadPool();
}

 

9. Java 层的binder机制

了解了native通信机制后,再去分析JAVA层的binder机制,就会很好理解了。它只是对native的binder做了一个封装。这一部分基本上没有太复杂的过程,这里不再赘述了。