Android自定义view-弹性ScrollView(上)
来源:互联网 发布:js显示display 编辑:程序博客网 时间:2024/05/23 20:14
前言:
相信很多安卓开发者都很头痛的一件事就是公司要求做的app和IOS端的风格保持一致,很多IOS端中具有弹性的ScrollView在android开发中不失为一个难题(相应的效果见IOS的微信端等等...)对应的软件可以查看。为了实现这样的效果,毫无疑问的一件事:可以考虑重写我们的ScrollView,然后在监听事件中做相应的处理。开题话说的太多了,直接开始我们的教程把。
正文:
首先贴上我们今天要完成的效果图:
看到这边我想大家应该都清楚今天要完成的是什么了吧。其实这个效果实现起来并不困难,重要的是对touch事件的处理,接下来我会给大家提供两套实现方案。一个是用基本的dispatchTouchEvent和TouchEvent去处理事件,另外一个则是用ViewDragHelper去处理view的事件。说实话,个人还是比较推崇第二种方法的,处理事件比较简单,而且还能做出各种想不到的特效。(Tips:对ViewDragHelper不熟悉的可以去百度,在以后的blog中会推出关于ViewDragHelper的讲解).
首先,我还是从难到易来实现吧,这样你才会体会到ViewDragHelper的方便之处。
Step1:继承ScrollVIew,重写ScrollView
public class FlexibleScrollView extends ScrollView {}Step2:重写构造器(3中构造,当然何时需要复写哪种构造请自行百度,常识性问题我觉得还是自己去查才能记住)
public FlexibleScrollView(Context context) {this(context, null);}public FlexibleScrollView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public FlexibleScrollView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}Step3:因为ScrollView是特殊的Viewgroup,在于它只能有一个childView,一般为LinearLayout来组成它的子view,所以我们需要复写onLayout方法,同时获取我们需要操作的子view。
@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (contentView == null)return;// scrollview唯一的一个子view的位置信息,这个位置信息在整个生命周期中保持不变originalRect.set(contentView.getLeft(), contentView.getTop(), contentView.getRight(), contentView.getBottom());}这里的originalRect是我们view的正常位置Rect范围,在整个生命周期中都是保持不变的。
我们的子view是在onFinishInflate() 中获取的,至于onFinishInflate() 什么时候加载,嘿嘿准备说百度的,算了,提一下吧:
onFinishInflate当View中所有的子控件均被映射成xml后触发,太深奥了?
比如你 自定义一个view叫myView ,路径是,com.test.view.MyView,此view是继承LinearLayout,定义的布局文件是my_view.xml
里面内容是:
<com.test.view.MyView>
<xxxx />
</com.test.view.MyView>
当你在使用的时候,可以这样使用
MyView mv = (MyView)View.inflate (context,R.layout.my_view,null);
当加载完成xml后,就会执行那个方法。
这段解释也是当时初学安卓的时候百度到的,然后就记下来了。自己太笨了,只能靠记笔记了,当然是在电脑上。
好了,看看代码吧:
/** * 在加载完xml后获取唯一的一个childview */@Overrideprotected void onFinishInflate() {if (getChildCount() > 0) {// 获取第一个childviewcontentView = getChildAt(0);}}好吧,一切准备工作就绪了,就差我们的事件处理了:
// 在触摸事件中处理上拉和下拉的逻辑@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if (contentView == null) {return super.dispatchTouchEvent(ev);}int action = ev.getAction();switch (action) {case MotionEvent.ACTION_DOWN:// 判断是否可以上拉或者下拉canPullDown = isCanPullDown();canPullUp = isCanPullUp();// 记录按下时的Y值startY = ev.getY();break;case MotionEvent.ACTION_UP:if (!isMoved)break; // 如果没有移动布局,则跳过执行// 开启动画TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(), originalRect.top);// 设置动画时间anim.setDuration(ANIM_TIME);// 给view设置动画contentView.setAnimation(anim);// 设置回到正常的布局位置contentView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom);// 将标志位重置canPullDown = false;canPullUp = false;isMoved = false;break;case MotionEvent.ACTION_MOVE:// 在移动过程既没有达到上拉的程度,又没有达到下拉的程度if (!canPullDown && !canPullUp) {startY = ev.getY();canPullDown = isCanPullDown();canPullUp = isCanPullUp();break;}// 计算手指移动的距离float nowY = ev.getY();int deltaY = (int) (nowY - startY);// 是否应该移动布局// 1.可以下拉,并且手指向下移动// 2.可以上拉,并且手指向上移动// 3.既可以上拉也可以下拉,当ScrollView包裹的控件比scrollView还小boolean shouldMove = (canPullDown && deltaY > 0) || (canPullUp && deltaY < 0) || (canPullDown && canPullUp);if (shouldMove) {// 计算偏移量int offset = (int) (deltaY * MOVE_FACTOR);contentView.layout(originalRect.left, originalRect.top + offset, originalRect.right, originalRect.bottom + offset);isMoved = true;}break;}return super.dispatchTouchEvent(ev);}/** * 判断是否滚动到顶部 * * @return */private boolean isCanPullDown() {return getScrollY() == 0 || contentView.getHeight() < getHeight() + getScrollY();}/** * 判断是否滚动到底部 */private boolean isCanPullUp() {return contentView.getHeight() <= getScrollY() + getHeight();}
这段代码有点长,分别是判断我们手指按下,手指移动和手指抬起时view的位置变化来重新onLayout。上面都有注解,相信不用花太多时间就能看懂。好了,整体代码我就贴上去吧。
activity_main.xml:
<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" > <com.beyole.view.FlexibleScrollView android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ImageView android:layout_width="match_parent" android:layout_height="200.0dip" android:scaleType="fitXY" android:src="@drawable/img1" /> <ImageView android:layout_width="match_parent" android:layout_height="200.0dip" android:scaleType="fitXY" android:src="@drawable/img2" /> <ImageView android:layout_width="match_parent" android:layout_height="200.0dip" android:scaleType="fitXY" android:src="@drawable/img3" /> </LinearLayout> </com.beyole.view.FlexibleScrollView></RelativeLayout>
就是简单的调用,和ScrollView一样。
FlexibleScrollView.java:
package com.beyole.view;import android.content.Context;import android.graphics.Rect;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.animation.TranslateAnimation;import android.widget.ScrollView;/** * 有弹性的scrollView * * @date 2016/02/22 * @author Iceberg * */public class FlexibleScrollView extends ScrollView {private static final String TAG = "FLEXIBLESCROLLVIEW";// 移动因子,是一个百分比,比如手指移动了100px,那么view只移动50px,目的是达到一个延迟的效果。private static final float MOVE_FACTOR = 0.5f;// 手指松开时,界面回到原始位置动画所需的时间private static final int ANIM_TIME = 300;// ScrollView唯一的一个子viewprivate View contentView;// 手指按下时的Y值,用于计算移动中的移动距离// 如果按下时不能上拉或者下拉,会在手指移动时更新为当前手指的Y值。private float startY;// 用于记录正常的布局位置private Rect originalRect = new Rect();// 记录手指按下时是否可以下拉private boolean canPullDown = false;// 记录手指按下时是否可以上拉private boolean canPullUp = false;// 在手指滑动时的过程中记录是否移动了布局private boolean isMoved = false;public FlexibleScrollView(Context context) {this(context, null);}public FlexibleScrollView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public FlexibleScrollView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}/** * 在加载完xml后获取唯一的一个childview */@Overrideprotected void onFinishInflate() {if (getChildCount() > 0) {// 获取第一个childviewcontentView = getChildAt(0);}}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (contentView == null)return;// scrollview唯一的一个子view的位置信息,这个位置信息在整个生命周期中保持不变originalRect.set(contentView.getLeft(), contentView.getTop(), contentView.getRight(), contentView.getBottom());}// 在触摸事件中处理上拉和下拉的逻辑@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if (contentView == null) {return super.dispatchTouchEvent(ev);}int action = ev.getAction();switch (action) {case MotionEvent.ACTION_DOWN:// 判断是否可以上拉或者下拉canPullDown = isCanPullDown();canPullUp = isCanPullUp();// 记录按下时的Y值startY = ev.getY();break;case MotionEvent.ACTION_UP:if (!isMoved)break; // 如果没有移动布局,则跳过执行// 开启动画TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(), originalRect.top);// 设置动画时间anim.setDuration(ANIM_TIME);// 给view设置动画contentView.setAnimation(anim);// 设置回到正常的布局位置contentView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom);// 将标志位重置canPullDown = false;canPullUp = false;isMoved = false;break;case MotionEvent.ACTION_MOVE:// 在移动过程既没有达到上拉的程度,又没有达到下拉的程度if (!canPullDown && !canPullUp) {startY = ev.getY();canPullDown = isCanPullDown();canPullUp = isCanPullUp();break;}// 计算手指移动的距离float nowY = ev.getY();int deltaY = (int) (nowY - startY);// 是否应该移动布局// 1.可以下拉,并且手指向下移动// 2.可以上拉,并且手指向上移动// 3.既可以上拉也可以下拉,当ScrollView包裹的控件比scrollView还小boolean shouldMove = (canPullDown && deltaY > 0) || (canPullUp && deltaY < 0) || (canPullDown && canPullUp);if (shouldMove) {// 计算偏移量int offset = (int) (deltaY * MOVE_FACTOR);contentView.layout(originalRect.left, originalRect.top + offset, originalRect.right, originalRect.bottom + offset);isMoved = true;}break;}return super.dispatchTouchEvent(ev);}/** * 判断是否滚动到顶部 * * @return */private boolean isCanPullDown() {return getScrollY() == 0 || contentView.getHeight() < getHeight() + getScrollY();}/** * 判断是否滚动到底部 */private boolean isCanPullUp() {return contentView.getHeight() <= getScrollY() + getHeight();}}MainActivity.java:
package com.beyole.flexiblescrollview;import android.app.Activity;import android.os.Bundle;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}}由于篇幅问题,第一种方法到此就结束了,其实整个逻辑不难理解。第二种用ViewDragHelper实现的,请移步:http://blog.csdn.net/smarticeberg/article/details/50717963
下载地址:http://download.csdn.net/detail/smarticeberg/9439380
Github地址:https://github.com/xuejiawei/beyole_FlexibleScrollView,欢迎fork or star
题外话:
android交流群:279031247(广告勿入)
新浪微博:SmartIceberg
- Android自定义view-弹性ScrollView(上)
- Android自定义view-弹性ScrollView(下)
- Android自定义弹性ScrollView
- Android上实现弹性ScrollView
- android自定义弹性ScrollVIew ,十分简单!
- Android上实现仿IOS弹性ScrollView
- Android上实现仿IOS弹性ScrollView
- Android上实现仿IOS弹性ScrollView
- Android上实现仿IOS弹性ScrollView
- Android上实现仿IOS弹性ScrollView
- Android上实现仿IOS弹性ScrollView
- Android上实现仿IOS弹性ScrollView
- Android上实现仿IOS弹性ScrollView
- Android上仿IOS弹性ScrollView
- 自定义ScrollView实现弹性ScrollView
- 自定义弹性的ScrollView
- 自定义弹性的ScrollView
- 自定义scrollview弹性布局
- 基于S3C6410的ARM11学习(六) 核心初始化之关闭所有中断
- codeforces 629 D. Babaei and Birthday Cake dp + 线段树
- Building Mapnik dependencies on Windows
- linux字符驱动之异步通知按键驱动
- 【算法总结】Binary Tree & Binary Search Tree 二叉树
- Android自定义view-弹性ScrollView(上)
- LeetCode之Intersection of two linked list不同方法
- 初识HTML
- NSMutableAttributedString同一个label中显示不同的字体样式
- 【android 开 发 】 - Android studio 下 NDK Jni 开发 简单例子
- 根据进程名批量杀死进程
- 关于intent中android.intent.action.USER_PRESENT的说明
- Hibernate 映射继承关系
- android studio教学视频资源(点开即看)