Android滑屏效果研究一
参考博文:
1、http://blog.csdn.net/qinjuning/article/details/7419207
2、http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html
3、http://blog.csdn.net/lzqjfly/article/details/11981611
4、最后再从头至尾实现一个slideMenu的效果
对于手机的滑屏效果,如:slideMenu、slidedrawer在实际项目中都是经常使用的,而且在实际项目中android提供的带滑动效果的控件不一定能完全能满足我们的项目的要求,为了能灵活地扩展我们想要的滑动效果,所以我们有必要对其原理进行研究,由于网上不少技术大牛已经对这个话题有过阐述,所以我直接当拿来主义了,拿来不可耻,重要的是充分利用网络上的资源,将其知识点变为自己的,下面主要是通过看别人写的博客,把里面提到的例子自己从头去实现一遍,相当于是练习,好了,话不多说,正式开始!
(【注意】这些练习是为后面实现一个综合的滑动效果打基础的,无目的的学习我是从不干的!!)
首先先得了解View绘制流程,可以阅读此博文:http://blog.csdn.net/qinjuning/article/details/7110211,里面对其onMeasure、onLayout、onDraw等常用的绘制方法有很详细的说明,里面也配合了一个综合的小例子来加深对绘制流程的理解,下面自己对其例子从头来实现一下,但是我是实现不一样的效果,但是是参考博文中的例子来做,这样学习才能更加扎实,该例子实际上是自定义ViewGroup,博文中实现的效果如下:
我要实现的是垂直排列,类似于LinerLayout中方向属性设置为android:orientation="vertical",我们知道LinerLayout就是继承ViewGroup的:
所以,也许它的实现原理,会跟我实现的有必然的联系,所以这些小例子,为之后系统分析android的UI视图有大的作用哟,下面先把要实现的效果用LinearLayout来呈现出来:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:gravity="center" android:text="test1" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:gravity="center" android:text="test2" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:gravity="center" android:text="test3" /> </LinearLayout>
效果如下:
下面,我们采用动态向ViewGroup添加控件,来利用上面学的各各绘制方法,实现同样的效果,首先新建一个自己的ViewGroup:
/** * 自定义ViewGroup,来实现LinerLayout同样的效果 */ public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context) { this(context, null); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub } }
将布局文件中的rootView声明成我们新建的ViewGroup:
好了,接下来,在MyViewGroup构造时,则用代码添加三个Button子元素,布局属性跟之前用LinerLayout中的一样:
/** * 自定义ViewGroup,来实现LinerLayout同样的效果 */ public class MyViewGroup extends ViewGroup { private Context context; public MyViewGroup(Context context) { this(context, null); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; init(); } private void init() { // 添加第一个button Button button1 = new Button(context); button1.setText("test1"); button1.setGravity(Gravity.CENTER); button1.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); this.addView(button1); // 添加第二个button Button button2 = new Button(context); button2.setText("test2"); button2.setGravity(Gravity.CENTER); button2.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); this.addView(button2); // 添加第三个button Button button3 = new Button(context); button3.setText("test3"); button3.setGravity(Gravity.CENTER); button3.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); this.addView(button3); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO }
这时候,onMeasure和onLayout两个方法目前都没有实现,我们这时运行一下,看会是什么效果:
可见,要想看到内容,需要我们去手动将添加在里面的子元素进行布局安放,所以onMeasure和onLayout就得派上用场了,代码如下:
/** * 自定义ViewGroup,来实现LinerLayout同样的效果 */ public class MyViewGroup extends ViewGroup { private Context context; public MyViewGroup(Context context) { this(context, null); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; init(); } private void init() { // 添加第一个button Button button1 = new Button(context); button1.setText("test1"); button1.setGravity(Gravity.CENTER); button1.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); this.addView(button1); // 添加第二个button Button button2 = new Button(context); button2.setText("test2"); button2.setGravity(Gravity.CENTER); button2.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); this.addView(button2); // 添加第三个button Button button3 = new Button(context); button3.setText("test3"); button3.setGravity(Gravity.CENTER); button3.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); this.addView(button3); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 获取该ViewGroup的实际长和宽 涉及到MeasureSpec类的使用 int specSize_Widht = MeasureSpec.getSize(widthMeasureSpec); int specSize_Heigth = MeasureSpec.getSize(heightMeasureSpec); //设置本ViewGroup的宽高 setMeasuredDimension(specSize_Widht, specSize_Heigth); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); // 获得每个对象的引用 // 或者可以调用ViewGroup父类方法measureChild()或者measureChildWithMargins()方法 this.measureChild(child, widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); int startLeft = 0;// 设置每个子View的起始横坐标 int startTop = 10; // 每个子View距离父视图的位置 , 简单设置为10px吧 。 可以理解为 android:marginTop=10px ; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); // 获得每个对象的引用 child.layout(startLeft, startTop, startLeft + child.getMeasuredWidth(), startTop + child.getMeasuredHeight()); startTop = startTop + child.getMeasuredHeight() + 10; // 校准startTop值,View之间的间距设为10px ; } } }
这时,再运行:
这样就实现了自定义的ViewGroup实现了类似于LinerLayout的效果,并且操练了很重要的两个绘制函数:onMeasure和onLayout。
另外,在实际工作中,可能对于ViewGroup中的子元素并非动态通过代码去创建的,而是事先在xml布局中定义好的,还是以上面这个效果为例(实际项目中可能里面的元素多种多样,但这里只是为了说明原理,不考虑很复杂得情况喽),布局文件修改为:
<com.example.testscrolleffect.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:gravity="center" android:text="test1" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:gravity="center" android:text="test2" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:gravity="center" android:text="test3" /> </com.example.testscrolleffect.MyViewGroup>
将MyViewGroup.java中的动态添加的代码去掉,而onMeasure和onLayout实现保留:
/** * 自定义ViewGroup,来实现LinerLayout同样的效果 */ public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context) { this(context, null); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 获取该ViewGroup的实际长和宽 涉及到MeasureSpec类的使用 int specSize_Widht = MeasureSpec.getSize(widthMeasureSpec); int specSize_Heigth = MeasureSpec.getSize(heightMeasureSpec); // 设置本ViewGroup的宽高 setMeasuredDimension(specSize_Widht, specSize_Heigth); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); // 获得每个对象的引用 // 或者可以调用ViewGroup父类方法measureChild()或者measureChildWithMargins()方法 this.measureChild(child, widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); int startLeft = 0;// 设置每个子View的起始横坐标 int startTop = 10; // 每个子View距离父视图的位置 , 简单设置为10px吧 。 可以理解为 android:marginTop=10px ; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); // 获得每个对象的引用 child.layout(startLeft, startTop, startLeft + child.getMeasuredWidth(), startTop + child.getMeasuredHeight()); startTop = startTop + child.getMeasuredHeight() + 10; // 校准startTop值,View之间的间距设为10px ; } } }
运行效果也一样:
至此,对于这些绘制过程就很清楚了,为下面的滑动初步实现奠定一个很好的基础,下面继续!
接下来,开始初步接触滑动效果的实现了,与其说是滑动效果,还不如说是界面的切换,也就是中间没有随手指过渡的效果,也就是为了学是更加扎实,将滑动的整个动作进行拆解,具体相关的进阶知识点请参考:http://blog.csdn.net/qinjuning/article/details/7247126,里面重点要理解scrollTo与scrollBy这两个方法的意义,为之后完整滑动效果的实现做很好的铺垫,还是以博客中例子进行操练,原博客实现的效果是左右横向页面的切换,如下:
为了融会贯通,我们来实现上下的切换效果,活用知识是很重要的,具体的实现原理其实博客上已经有说明,下面来简单画一下实现上下切换的原理:
代码还是基于上个实验的,修改布局文件:
<com.example.testscrolleffect.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <!-- 第一屏内容 --> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FFFF0000" android:gravity="center" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ViewGroup1" /> </LinearLayout> <!-- 第二屏内容 --> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FFFFFF00" android:gravity="center" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ViewGroup2" /> </LinearLayout> <!-- 第三屏内容 --> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FF0000FF" android:gravity="center" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ViewGroup3" /> </LinearLayout> </com.example.testscrolleffect.MyViewGroup>
由于在ViewGroup对子布局进行摆放时,是需要获得屏幕的宽高的,所以在MainActivity先获得:
public class MainActivity extends Activity { public static int screenWidth; // 屏幕宽度 public static int scrrenHeight; // 屏幕高度 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 获得屏幕分辨率大小 DisplayMetrics metric = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metric); screenWidth = metric.widthPixels; scrrenHeight = metric.heightPixels; } }
修改MyViewGroup中的onMesure和onLayout方法,将子元素摆放成图中所画的,首先只有ViewGroup1在布局坐标中,其它两个在它之下处于视图坐标中,具体写法跟第一个实验差不多,应该很容易读懂:
/** * 自定义ViewGroup,来实现LinerLayout同样的效果 */ public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context) { this(context, null); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 获取该ViewGroup的实际长和宽 涉及到MeasureSpec类的使用 int specSize_Widht = MeasureSpec.getSize(widthMeasureSpec); int specSize_Heigth = MeasureSpec.getSize(heightMeasureSpec); // 设置本ViewGroup的宽高 setMeasuredDimension(specSize_Widht, specSize_Heigth); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); // 设置每个子视图的大小 , 即全屏 child.measure(MainActivity.screenWidth, MainActivity.scrrenHeight); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); int startLeft = 0;// 设置每个子View的起始横坐标 int startTop = 0; // 每个子View距离父视图的位置 for (int i = 0; i < childCount; i++) { View child = getChildAt(i); // 获得每个对象的引用 child.layout(startLeft, startTop, startLeft + MainActivity.screenWidth, startTop + MainActivity.scrrenHeight); startTop = startTop + MainActivity.scrrenHeight; // 校准startTop值 } } }
这时看一下效果:
由于这一节中,还没有加入真正的滑动事件,所以先用按钮点击,来对添加的几个布局进行切换,中间的滑动过渡效果也暂时没有,先添加两个操作button,用来进行切换:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <com.example.testscrolleffect.MyViewGroup android:id="@+id/lay_view_group" android:layout_width="fill_parent" android:layout_height="fill_parent" > <!-- 第一屏内容 --> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FFFF0000" android:gravity="center" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ViewGroup1" /> </LinearLayout> <!-- 第二屏内容 --> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FFFFFF00" android:gravity="center" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ViewGroup2" /> </LinearLayout> <!-- 第三屏内容 --> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FF0000FF" android:gravity="center" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ViewGroup3" /> </LinearLayout> </com.example.testscrolleffect.MyViewGroup> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" > <Button android:id="@+id/btn_next" android:layout_width="0dip" android:layout_height="fill_parent" android:layout_weight="1" android:text="next" /> <Button android:id="@+id/btn_pre" android:layout_width="0dip" android:layout_height="fill_parent" android:layout_weight="1" android:text="pre" /> </LinearLayout> </RelativeLayout>
显示如下:
处理点击事件,来实现三屏内容的切换,注意,这是上下之间的切换,而不是左右:
package com.example.testscrolleffect; import android.app.Activity; import android.os.Bundle; import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.Toast; public class MainActivity extends Activity implements OnClickListener { public static int screenWidth; // 屏幕宽度 public static int scrrenHeight; // 屏幕高度 private int curScreen = 0; // 当前位于第几屏幕 ,共3个"屏幕", 3个LinearLayout private ViewGroup lay_view_group; private Button btn_next;// 切换下一屏 private Button btn_pre;// 切换下一页 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 获得屏幕分辨率大小 DisplayMetrics metric = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metric); screenWidth = metric.widthPixels; scrrenHeight = metric.heightPixels; lay_view_group = (ViewGroup) findViewById(R.id.lay_view_group); btn_next = (Button) findViewById(R.id.btn_next); btn_pre = (Button) findViewById(R.id.btn_pre); btn_next.setOnClickListener(this); btn_pre.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_next: if (curScreen < 2) { // 防止屏幕越界 curScreen++; Toast.makeText(this, "第" + (curScreen + 1) + "屏", 300).show(); } else { Toast.makeText(this, "当前已是最后一屏", Toast.LENGTH_SHORT).show(); } lay_view_group.scrollTo(0, curScreen * scrrenHeight);//利用它来对切换内容至坐标视图区 break; case R.id.btn_pre: if (curScreen > 0) { // 防止屏幕越界 curScreen--; Toast.makeText(this, "第" + (curScreen + 1) + "屏", 300).show(); } else { Toast.makeText(this, "当前已是第一屏", Toast.LENGTH_SHORT).show(); } lay_view_group.scrollTo(0, curScreen * scrrenHeight); break; } } }
效果如下:
现在我们已经可以通过自定义view来实实现内容区域的切换,下面则真正一步步实现带滑动效果的切换,看好了!
在实现滑屏效果之前,需要具备一些知识点,可以参考博文:http://blog.csdn.net/qinjuning/article/details/7419207,这里面对其滑屏相关的知识点进行了很详细的说明,其里面实现的效果为:
而做为学习,就得灵活去用它,所以基于这种效果,我要实现上下屏幕滑动的切换,通过这个练习,我想对于滑动的机制就会理解得非常深刻,注意:在做这个练习之前,一定得要先对着博文,来实现横向的切换效果,也就是对博文中所涉及到的知识点完全透析了才行。
以下分为两步来实现,第一步实现通过按钮来操纵滑动,而非用手指去触摸,这个会放到第二步去完成,因为稍复杂一些,下面正式开始进入我们的滑动世界啦!
...
紧接着第二步则是真正滑动了,通过手指的触摸来实现滑动,如果滑动力度不够则会滑回原点,也是非常非常经典的滑动切换的效果,完整地从零开始实现此效果,有助于之后实现更复杂的滑动效果奠定很好的基础,下面正式开始:
...
【提示】:关于如何实现ViewPager的效果会在新的博文中体现,可以参考:http://www.cnblogs.com/webor2006/p/4677181.html,这里可能就不会更新了。