Android_Scroller滑动动画
2016-07-07 08:44
483 查看
转载请注明出处:http://blog.csdn.net/y22222ly/article/details/51842218
当我们在使用view的scrollTo()或scrollBy()时,会发现这个滑动很生硬,没有动画效果,一下就过去了,就像我前篇文章提到的那样。如果能平滑的滑动回去的话,最好不过了,刚好安卓提供一个Scroller类,专门来处理view在scrollTo()或scrollBy()时没有滑动效果的问题。
该大神详细说明了各个方法的用法。
使用Scroller分三步:
1. 声明与初始化
2. 调用startScroll()一定要注意4个参数,前2个为当前滑动偏移量,后2个为需要滑动的偏移量
3. 重写view的computeScroll()方法,调用mScroller.computeScrollOffset()计算滑动值
4. 使用计算后的Scroller实例,调用view的scrollTo()进行滑动
效果图:
源码:
Scroller就像属性动画中的ValueAnimator,他只产生一段连续的值,而不做处理,需要view根据自身的情况去处理这些值,目前看好像只用在scrollTo()或scrollBy()中。
还可以控制动画时间,调用5个参数的startScroll()即可:
实现起来并不难,主要解决的问题就是:如何处理滑动冲突:左右滑动时,若上下滑动父控件(ListView)会拦截滑动事件,去做上下滑动。这时,子view(ViewGroup)就只接收到一个
如何让子view在滑动时,提醒父控件不要拦截事件,将事件传递下去呢?我之前在子View中,设置一个flag用来标识view在滑动状态,然后ListView去根据这个值判断是否拦截滑动事件,这样做起来有些bug,在偶然看listview源码时,看到一个方法可以请求父控件不要拦截touch事件,这个事件就是requestDisallowInterceptTouchEvent(true),在AbsListView.startScrollIfNeeded()方法中。
有了这个方法,实现起来简单很多,只要在左右开始滑动时,请求父类不要拦截,我们自己讲touch事件消耗即可。记住要在ACTION_UP时,将其还原为false.直接上代码:
GestureDemoView:
GestureActivity:
item的布局文件:
Activity的布局文件:
当我们在使用view的scrollTo()或scrollBy()时,会发现这个滑动很生硬,没有动画效果,一下就过去了,就像我前篇文章提到的那样。如果能平滑的滑动回去的话,最好不过了,刚好安卓提供一个Scroller类,专门来处理view在scrollTo()或scrollBy()时没有滑动效果的问题。
Scroller基本使用
下例源码引用自:http://ipjmc.iteye.com/blog/1615828该大神详细说明了各个方法的用法。
import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.widget.LinearLayout; import android.widget.Scroller; public class CustomView extends LinearLayout { private static final String TAG = "Scroller"; private Scroller mScroller; public CustomView(Context context, AttributeSet attrs) { super(context, attrs); mScroller = new Scroller(context); } //调用此方法滚动到目标位置 public void smoothScrollTo(int fx, int fy) { int dx = fx - mScroller.getFinalX(); int dy = fy - mScroller.getFinalY(); smoothScrollBy(dx, dy); } //调用此方法设置滚动的相对偏移 public void smoothScrollBy(int dx, int dy) { //设置mScroller的滚动偏移量 mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy); invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果 } @Override public void computeScroll() { //先判断mScroller滚动是否完成 if (mScroller.computeScrollOffset()) { //这里调用View的scrollTo()完成实际的滚动 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //必须调用该方法,否则不一定能看到滚动效果 postInvalidate(); } super.computeScroll(); } }
使用Scroller分三步:
1. 声明与初始化
2. 调用startScroll()一定要注意4个参数,前2个为当前滑动偏移量,后2个为需要滑动的偏移量
3. 重写view的computeScroll()方法,调用mScroller.computeScrollOffset()计算滑动值
4. 使用计算后的Scroller实例,调用view的scrollTo()进行滑动
Scroller实战
根据上述代码后自己学习后,对前一篇文章提到的滑动处理scrollTo()问题做了实现。效果图:
源码:
package com.example.y2222.myview; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; import android.widget.Scroller; import com.example.y2222.myapplication.R; /** * Created by raise.yang on 2016/06/29. */ public class GestureDemoView extends LinearLayout { //1,定义GestureDetector类 private GestureDetector m_gestureDetector; //1,定义Scroller类 private Scroller m_scroller; private int m_max_scrollX; public GestureDemoView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public GestureDemoView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //设置为可点击 setClickable(true); //2,初始化手势类,同时设置手势监听 m_gestureDetector = new GestureDetector(context, onGestureListener); LayoutInflater.from(context).inflate(R.layout.view_gesture, this); // 2,初始化Scroller类,可自定义插值器 m_scroller = new Scroller(getContext(), new LinearInterpolator()); } @Override public boolean onTouchEvent(MotionEvent event) { //3,将touch事件交给gesture处理 m_gestureDetector.onTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_UP) { // GestureDetector没有处理up事件的方法,只能在这里处理了。 int scrollX = getScrollX(); if (scrollX > m_max_scrollX / 2) { show_right_view(); } else { hide_right_view(); } } return super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { //测量子view的宽高,?不测量,右侧布局会不显示,这里有点疑问 measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec); if (i == 1) { m_max_scrollX = getChildAt(i).getMeasuredWidth(); } } } //初始化手势监听对象,使用GestureDetector.OnGestureListener的实现抽象类,因为实际开发中好多方法用不上 private final GestureDetector.OnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { Log.d("GestureDemoView", "onScroll() distanceX = " + distanceX + " getScrollX = " + getScrollX() + " max_scrollX = " + m_max_scrollX); int scrollX = getScrollX(); int minScrollX = -scrollX; int maxScrollY = m_max_scrollX - scrollX; // 对滑动的距离边界控制 if (distanceX > maxScrollY) { distanceX = maxScrollY; } else if (distanceX < minScrollX) { distanceX = minScrollX; } // m_scroller.startScroll(m_scroller.getFinalX(), m_scroller.getFinalY(), m_scroller.getFinalX() + (int) distanceX, 0); scrollBy((int) distanceX, 0); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { Log.d("GestureDemoView", "onFling() velocityX = " + velocityX); if (velocityX < 0) { //快速向左滑动 show_right_view(); } else { hide_right_view(); } return super.onFling(e1, e2, velocityX, velocityY); } }; private void show_right_view() { //3,开启滑动条 // 注意:前两个参数为起始滑动点,后2个参数为滑动距离 m_scroller.startScroll(getScrollX(), 0, m_max_scrollX-getScrollX(), 0); //这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果 invalidate(); // scrollTo(m_max_scrollX, 0); } private void hide_right_view() { // 注意:前两个参数为起始滑动点,后2个参数为滑动距离 m_scroller.startScroll(getScrollX(), 0, 0 - getScrollX(), 0); invalidate(); // scrollTo(0, 0); } @Override public void computeScroll() { //4,调用view自身的scrollTo()滑动 if (m_scroller.computeScrollOffset()) {//计算滑动值,并判断是否结束false表示结束 // 两个参数为相对父布局的绝对坐标,这里去Scroller计算出来的当前滑动值 scrollTo(m_scroller.getCurrX(), m_scroller.getCurrY()); postInvalidate(); } } }
Scroller就像属性动画中的ValueAnimator,他只产生一段连续的值,而不做处理,需要view根据自身的情况去处理这些值,目前看好像只用在scrollTo()或scrollBy()中。
还可以控制动画时间,调用5个参数的startScroll()即可:
/** * Start scrolling by providing a starting point, the distance to travel, * and the duration of the scroll. * * @param startX Starting horizontal scroll offset in pixels. Positive * numbers will scroll the content to the left. * @param startY Starting vertical scroll offset in pixels. Positive numbers * will scroll the content up. * @param dx Horizontal distance to travel. Positive numbers will scroll the * content to the left. * @param dy Vertical distance to travel. Positive numbers will scroll the * content up. * @param duration Duration of the scroll in milliseconds. */ public void startScroll(int startX, int startY, int dx, int dy, int duration) { }
ListView滑动删除
单个item的滑动效果已经出来了,那么顺便将其集成在ListView中,做成滑动删除效果也不错,效果图如下:实现起来并不难,主要解决的问题就是:如何处理滑动冲突:左右滑动时,若上下滑动父控件(ListView)会拦截滑动事件,去做上下滑动。这时,子view(ViewGroup)就只接收到一个
MotionEvent.ACTION_CANCEL事件。
如何让子view在滑动时,提醒父控件不要拦截事件,将事件传递下去呢?我之前在子View中,设置一个flag用来标识view在滑动状态,然后ListView去根据这个值判断是否拦截滑动事件,这样做起来有些bug,在偶然看listview源码时,看到一个方法可以请求父控件不要拦截touch事件,这个事件就是requestDisallowInterceptTouchEvent(true),在AbsListView.startScrollIfNeeded()方法中。
有了这个方法,实现起来简单很多,只要在左右开始滑动时,请求父类不要拦截,我们自己讲touch事件消耗即可。记住要在ACTION_UP时,将其还原为false.直接上代码:
GestureDemoView:
package com.example.y2222.myview; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; import android.widget.Scroller; import android.widget.TextView; import com.example.y2222.myapplication.R; /** * Created by raise.yang on 2016/06/29. */ public class GestureDemoView extends LinearLayout { private GestureDetector m_gestureDetector; private Scroller m_scroller; private int m_max_scrollX; private TextView m_primary; public GestureDemoView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public GestureDemoView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //设置为可点击 setClickable(true); m_gestureDetector = new GestureDetector(context, onGestureListener); m_gestureDetector.setIsLongpressEnabled(false); LayoutInflater.from(context).inflate(R.layout.view_gesture, this); m_scroller = new Scroller(getContext(), new LinearInterpolator()); } @Override protected void onFinishInflate() { super.onFinishInflate(); m_primary = (TextView) findViewById(R.id.primary_text); } public void setPrimaryText(String text) { m_primary.setText(text); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d("GestureDemoView", "onTouchEvent() "); m_gestureDetector.onTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_UP) { // 抬起手指,请求父类可以拦截touch事件 requestDisallowInterceptTouchEvent(false); int scrollX = getScrollX(); if (scrollX > m_max_scrollX / 2) { show_right_view(); } else { hide_right_view(); } } // 返回true,将此事件消耗,父类就不会处理onTouchEvent事件 return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec); if (i == 1) { m_max_scrollX = getChildAt(i).getMeasuredWidth(); } } } private final GestureDetector.OnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { Log.d("GestureDemoView", "onScroll() distanceX = " + distanceX + " getScrollX = " + getScrollX() + " max_scrollX = " + m_max_scrollX); int scrollX = getScrollX(); int minScrollX = -scrollX; int maxScrollY = m_max_scrollX - scrollX; // 对滑动的距离边界控制 if (distanceX > maxScrollY) { distanceX = maxScrollY; } else if (distanceX < minScrollX) { distanceX = minScrollX; } scrollBy((int) distanceX, 0); //当前view开始滑动,请求父类不要拦截move事件 requestDisallowInterceptTouchEvent(true); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { Log.d("GestureDemoView", "onFling() velocityX = " + velocityX); if (velocityX < 0) { //快速向左滑动 show_right_view(); } else { hide_right_view(); } return super.onFling(e1, e2, velocityX, velocityY); } }; private void show_right_view() { m_scroller.startScroll(getScrollX(), 0, m_max_scrollX - getScrollX(), 0, 100); invalidate(); } private void hide_right_view() { m_scroller.startScroll(getScrollX(), 0, 0 - getScrollX(), 0, 100); invalidate(); } @Override public void computeScroll() { if (m_scroller.computeScrollOffset()) {//计算滑动值,并判断是否结束false表示结束 scrollTo(m_scroller.getCurrX(), m_scroller.getCurrY()); postInvalidate(); } } public void initViewPosition() { scrollTo(0, 0); } }
GestureActivity:
package com.example.y2222.myapplication; import android.app.Activity; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ListView; import com.example.y2222.myview.GestureDemoView; public class GestureActivity extends Activity { private ListView m_listView; private String[] m_datas = new String[]{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gesture); m_listView = (ListView) findViewById(R.id.listview); m_listView.setAdapter(new Adapter()); } class Adapter extends BaseAdapter { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(GestureActivity.this).inflate(R.layout.item_slide_listview, parent, false); } GestureDemoView root = (GestureDemoView) convertView; root.setPrimaryText(getItem(position)); root.initViewPosition(); return convertView; } @Override public int getCount() { return m_datas.length; } @Override public String getItem(int position) { return m_datas[position]; } @Override public long getItemId(int position) { return position; } } }
item的布局文件:
<?xml version="1.0" encoding="utf-8"?> <com.example.y2222.myview.GestureDemoView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="80dp"> </com.example.y2222.myview.GestureDemoView>
Activity的布局文件:
<?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" android:background="@android:color/holo_blue_light" tools:context="com.example.y2222.myapplication.GestureActivity"> <ListView android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories