首先,这是原代码的链接:http://www.eoeandroid.com/thread-545123-1-1.html
可惜的是,当初原作者只上了代码,并没有作解释。一开始理解的时候的确有点费劲。于是这篇算作是译文吧,基本是自己的理解,所以也许有点出入,当然了,也加入了ActionBar的部分知识点。
一、分析一下结构
1个Activity以及它的2个Fragment,布局是对应的,还有一个自定义的ActionProvider,background_gradient是一个自定义的<shape>
关于Fragment的完全解析:http://blog.youkuaiyun.com/lmj623565791/article/details/37970961
二、原理分析
其实呢,原理并不复杂。就是将两个Fragment放在Activity里,通过SlidingPaneLayout这个组件(在v4包里),实现监听滑动达到从右Fragment完全打开到左Fragment完全打开的过程。当中实现动态地改变外边距(Margin)
三、新建两个Fragment
以左侧(菜单)Fragment为例
public class MenuFragment extends Fragment {
private View fragmentView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
fragmentView = inflater.inflate(R.layout.slidingpane_menu, container,
false);
return fragmentView;
}
public View getFragmentView() {
return fragmentView;
}
}
这边还是很好理解的,只要用LayoutInflater类的inflate方法把不觉导入,为Fragmemnt创建自己的布局即可。
getFragment方法在接下来的MainActivity中会用到,先放着。
右侧(内容)Fragment差不多
public class ContentFragment extends Fragment {
private View fragmentView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
fragmentView = inflater.inflate(R.layout.slidingpane_content,
container, false);
return fragmentView;
}
public FrameLayout.LayoutParams getCurrentParams() {
return (LayoutParams) fragmentView.getLayoutParams();
}
public void setCurrentParams(FrameLayout.LayoutParams params) {
fragmentView.setLayoutParams(params);
}
}
自定义一组get、set方法同样稍后解释
两个布局文件,首先是左侧Fragment的
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="30dp" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/io" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text=" 小精灵"
android:textColor="@android:color/white"
android:textSize="20sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="@android:color/white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_weight="1"
android:orientation="vertical" >
<Button
android:id="@+id/btn01"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:drawableLeft="@drawable/icon01"
android:gravity="left|center"
android:text="松饼人"
android:textColor="@android:color/white"
android:textSize="20sp" />
<Button
android:id="@+id/btn02"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:drawableLeft="@drawable/icon02"
android:gravity="left|center"
android:text="圣诞树"
android:textColor="@android:color/white"
android:textSize="20sp" />
<Button
android:id="@+id/btn03"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:drawableLeft="@drawable/icon03"
android:gravity="left|center"
android:text="雪人"
android:textColor="@android:color/white"
android:textSize="20sp" />
<Button
android:id="@+id/btn04"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:drawableLeft="@drawable/icon04"
android:gravity="left|center"
android:text="礼物"
android:textColor="@android:color/white"
android:textSize="20sp" />
<Button
android:id="@+id/btn05"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:drawableLeft="@drawable/icon05"
android:gravity="left|center"
android:text="糖果"
android:textColor="@android:color/white"
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@null"
android:drawableLeft="@drawable/icon_mini01"
android:gravity="left|center"
android:text="铲子"
android:textColor="@android:color/darker_gray" />
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@null"
android:drawableLeft="@drawable/icon_mini02"
android:gravity="left|center"
android:text="斧头"
android:textColor="@android:color/darker_gray" />
</LinearLayout>
</LinearLayout>
接着是右侧Fragment的
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@android:color/black" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是内容部分"
android:layout_gravity="center_horizontal"
android:textColor="@android:color/white"
android:textSize="25sp"/>
</LinearLayout>
这里有一个注意点哦!!右侧Fragment布局的背景色设为任意颜色,而左侧Fragment布局的颜色设为透明。为什么呢?我就不多解释,稍微改一下,跑跑看就知道喽~!四、完成Activity的布局文件
先上代码,再一点点分析
<FrameLayout 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="com.dota.example.fishychenofslidingpanelayout.MainActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_darksider" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_gradient" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"
android:background="@android:color/black" />
</LinearLayout>
<android.support.v4.widget.SlidingPaneLayout
android:id="@+id/slidingPaneLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent" >
<FrameLayout
android:id="@+id/menuLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="100dp" />
<FrameLayout
android:id="@+id/contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SlidingPaneLayout>
</FrameLayout>
关于结构呢,我给个更清晰的图这边要用到帧布局(FrameLayout)
关于FrameLayout,这是个最简单的布局,所有的控件都会自动排到左上角,可能你会问了,这有什么用呢?首先,它很简单,吃的系统资源很少。其次,很很好的满足我们的需求,就像是ps里的图层一样。
好了,接着我们分析布局原理。我们定义一个内嵌的LinearLayout,在内部再写一个FrameLayotu和一个空View,高度比为1:3。空View填充黑色。子FrameLayout里填两个空View,一个背景是图片,一个是渐变背景,用来覆盖在前者上。关于<shape>,如下所示,用来做一个自定义形状的背景。
几个属性简单说一下:<corners>用来生成圆角矩形
<padding>用来设置内边距
<size>用来设置尺寸
<solid>填充色
<stroke>边框线
<gradient>渐变色
我这边只用到了最后一条,分别设置起始色,结束色,以及变化角度(270是自上而下)
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient
android:startColor="@android:color/transparent"
android:endColor="@android:color/black"
android:angle="270"/>
</shape>
背景的效果如下接着把SlidingPaneLayout放上去(FrameLayout里后来的会覆盖在先写的),里面再放两个帧布局。还记得之前说的吗?实现的是右侧Fragment完整显示到左侧Fragment完整显示。所以,这边给左边的布局一个MarginRight属性
五、主Activity
这边就是写关于变换的地方了
先实例化一个DisplayMetrics对象,接着我们要用这个来获取一些屏幕的显示信息
private DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
这样,我们就可以用metrics的一些field来获得屏幕的一些显示信息了
完整地看一下,声明的变量
private DisplayMetrics metrics = new DisplayMetrics();
private SlidingPaneLayout slidingPaneLayout;
private MenuFragment menuFragment;
private ContentFragment contentFragment;
private int maxMargin = 0;
private ActionBar actionBar;
oncreate()方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initActionBar();
initPaneLayout();
slidingPaneLayout.setPanelSlideListener(new PanelSlideListener() {
@Override
public void onPanelSlide(View arg0, float percent) {
int contentMargin = (int) (percent * maxMargin);
FrameLayout.LayoutParams params = contentFragment
.getCurrentParams();
params.setMargins(0, 0, 0, contentMargin);
contentFragment.setCurrentParams(params);
float scale = 1 - ((1 - percent) * maxMargin * 4)
/ (float) metrics.heightPixels;
menuFragment.getFragmentView().setScaleX(scale);
menuFragment.getFragmentView().setScaleY(scale);
menuFragment.getFragmentView().setPivotX(0);
menuFragment.getFragmentView().setPivotY(
metrics.heightPixels / 2);
menuFragment.getFragmentView().setAlpha(percent);
}
@Override
public void onPanelOpened(View arg0) {
// TODO Auto-generated method stub
}
@Override
public void onPanelClosed(View arg0) {
// TODO Auto-generated method stub
}
});
}
好,我们一点点看。
initActionBar()方法我就不讲了。里面是我自己写的关于actionbar的一些操作,可以参考我写的关于ActionBar的学习 http://blog.youkuaiyun.com/zerolovesc1993/article/details/43338675
接着我们看initPaneLayout()方法,用来初始化SlidingPaneLayout的
private void initPaneLayout() {
getWindowManager().getDefaultDisplay().getMetrics(metrics);
slidingPaneLayout = (SlidingPaneLayout) findViewById(R.id.slidingPaneLayout);
menuFragment = new MenuFragment();
contentFragment = new ContentFragment();
FragmentTransaction transaction = getFragmentManager()
.beginTransaction();
transaction.replace(R.id.menuLayout, menuFragment);
transaction.replace(R.id.contentLayout, contentFragment);
transaction.commit();
maxMargin = metrics.heightPixels / 5;
}
关于Fragment的话,用一个FragmentTransaction对象的replace方法,来将activity_main中的控件和Fragment绑起来
完成之后用commit方法提交。当然,再此之前需要得到一个FragmentManager类的对象来开始事务(beginTransaction)。这边的maxMargin定的是屏幕高的5分之1,有什么用,我们接下来说。
我们基本快完成了,为我们的slidingpaneLayout组件设置对应的滑动监听器
setPanelSlideListener
完成3个抽象方法,我们只要写滑动监听的那个
@Override
public void onPanelSlide(View arg0, float percent) {
}
float percent对应的那个是啥呢?是一个0~1的浮点型,用来模拟一个滑动进度条一样(可以这么理解,所以我把变量名改成了percent)<span style="white-space:pre"> </span>int contentMargin = (int) (percent * maxMargin);
FrameLayout.LayoutParams params = contentFragment
.getCurrentParams();
params.setMargins(0, 0, 0, contentMargin);
contentFragment.setCurrentParams(params);
这一段用来设置右边的动态变化的。原理很简单,先得到右侧的LayoutParams,这个东西相当于一个layout的信息包,里面封装了关于layout的信息。再重置Margin,四个参数对应左、上、右、下,这边的contentMargin就是我们根据前边定的maxMargin结合percent来实现动态根据滑动距离改变右侧的下边距。
接着是左边的动态变化的
float scale = 1 - ((1 - percent) * maxMargin * 4)
/ (float) metrics.heightPixels;
menuFragment.getFragmentView().setScaleX(scale);
menuFragment.getFragmentView().setScaleY(scale);
menuFragment.getFragmentView().setPivotX(0);
menuFragment.getFragmentView().setPivotY(
metrics.heightPixels / 2);
menuFragment.getFragmentView().setAlpha(percent);
用到了三个方法,setScale用来设置变换的 1代表不变,setPivot 用来设置变换的起点的(从左侧的当中开始),setAlpha用来设置透明度,float 0为不可见,1为100%显示。X,Y分别对象变换的坐标,至于scale,是自己改的,要是嫌麻烦,可以直接用percent,不过效果没这个好。(一开始的时候,就有一个不错的大小,用percent的话,一开始的大小太小了)
Activity完整的代码
public class MainActivity extends Activity {
private DisplayMetrics metrics = new DisplayMetrics();
private SlidingPaneLayout slidingPaneLayout;
private MenuFragment menuFragment;
private ContentFragment contentFragment;
private int maxMargin = 0;
private ActionBar actionBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initActionBar();
initPaneLayout();
slidingPaneLayout.setPanelSlideListener(new PanelSlideListener() {
@Override
public void onPanelSlide(View arg0, float percent) {
int contentMargin = (int) (percent * maxMargin);
FrameLayout.LayoutParams params = contentFragment
.getCurrentParams();
params.setMargins(0, 0, 0, contentMargin);
contentFragment.setCurrentParams(params);
float scale = 1 - ((1 - percent) * maxMargin * 4)
/ (float) metrics.heightPixels;
menuFragment.getFragmentView().setScaleX(scale);
menuFragment.getFragmentView().setScaleY(scale);
menuFragment.getFragmentView().setPivotX(0);
menuFragment.getFragmentView().setPivotY(
metrics.heightPixels / 2);
menuFragment.getFragmentView().setAlpha(percent);
}
@Override
public void onPanelOpened(View arg0) {
// TODO Auto-generated method stub
}
@Override
public void onPanelClosed(View arg0) {
// TODO Auto-generated method stub
}
});
}
private void initPaneLayout() {
getWindowManager().getDefaultDisplay().getMetrics(metrics);
slidingPaneLayout = (SlidingPaneLayout) findViewById(R.id.slidingPaneLayout);
menuFragment = new MenuFragment();
contentFragment = new ContentFragment();
FragmentTransaction transaction = getFragmentManager()
.beginTransaction();
transaction.replace(R.id.menuLayout, menuFragment);
transaction.replace(R.id.contentLayout, contentFragment);
transaction.commit();
maxMargin = metrics.heightPixels / 5;
}
private void initActionBar() {
actionBar = getActionBar();
actionBar.setHomeButtonEnabled(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_actionbar, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
slidingPaneLayout.openPane();
break;
case R.id.menu_candy:
slidingPaneLayout.closePane();
break;
default:
break;
}
return true;
}
}
其中写到的openPane和closePane方法是对应的,用来打开左侧Fragment和关闭。其中,要用到actionBar.setHomeButtonEnabled(true)方法才能让home按钮可以被点击
百度云链接:
http://pan.baidu.com/s/1hqIPC6S