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,这里可能就不会更新了。

posted on 2014-03-12 17:00  cexo  阅读(400)  评论(0编辑  收藏  举报

导航