Android自定义控件——有弹性的ListView,ScrollView
上一次我们试验了有弹性的ScrollView。详情
这一次,我们来试验有弹性的ScrollView。
国际惯例,效果图:
主要代码:
- import android.content.Context;
- import android.graphics.Rect;
- import android.util.AttributeSet;
- import android.view.MotionEvent;
- import android.view.animation.Animation;
- import android.view.animation.Animation.AnimationListener;
- import android.view.animation.TranslateAnimation;
- import android.widget.AbsListView;
- import android.widget.ListView;
- /**
- * ElasticScrollView有弹性的ListView
- */
- public class ElasticListView extends ListView {
- private float y;
- private Rect normal = new Rect();
- private boolean animationFinish = true;
- public ElasticListView(Context context) {
- super(context);
- init();
- }
- public ElasticListView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- }
- boolean overScrolled = false;
- private void init() {
- setOnScrollListener(new OnScrollListener() {
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- }
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- overScrolled = false;
- }
- });
- }
- @Override
- protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
- overScrolled = true;
- }
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- commOnTouchEvent(ev);
- return super.onTouchEvent(ev);
- }
- public void commOnTouchEvent(MotionEvent ev) {
- if (animationFinish) {
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- y = ev.getY();
- break;
- case MotionEvent.ACTION_UP:
- y = 0;
- if (isNeedAnimation()) {
- animation();
- }
- break;
- case MotionEvent.ACTION_MOVE:
- final float preY = y == 0 ? ev.getY() : y;
- float nowY = ev.getY();
- int deltaY = (int) (preY - nowY);
- y = nowY;
- // 当滚动到最上或者最下时就不会再滚动,这时移动布局
- if (isNeedMove(deltaY)) {
- if (normal.isEmpty()) {
- // 保存正常的布局位置
- normal.set(getLeft(), getTop(), getRight(), getBottom());
- }
- // 移动布局
- layout(getLeft(), getTop() - deltaY / 2, getRight(), getBottom() - deltaY / 2);
- }
- break;
- default:
- break;
- }
- }
- }
- // 开启动画移动
- public void animation() {
- // 开启移动动画
- TranslateAnimation ta = new TranslateAnimation(0, 0, 0, normal.top - getTop());
- ta.setDuration(200);
- ta.setAnimationListener(new AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {
- animationFinish = false;
- }
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
- @Override
- public void onAnimationEnd(Animation animation) {
- clearAnimation();
- // 设置回到正常的布局位置
- layout(normal.left, normal.top, normal.right, normal.bottom);
- normal.setEmpty();
- animationFinish = true;
- }
- });
- startAnimation(ta);
- }
- // 是否需要开启动画
- public boolean isNeedAnimation() {
- return !normal.isEmpty();
- }
- // 是否需要移动布局
- public boolean isNeedMove(float deltaY) {
- if (overScrolled && getChildCount() > 0) {
- if (getLastVisiblePosition() == getCount() - 1 && deltaY > 0) {
- return true;
- }
- if (getFirstVisiblePosition() == 0 && deltaY < 0) {
- return true;
- }
- }
- return false;
- }
- }
测试代码:
- public class MainActivity extends Activity {
- ElasticListView listView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- listView = (ElasticListView) findViewById(R.id.listview);
- String[] listValues = new String[20];
- for (int i=0;i<listValues.length;i++) {
- listValues[i] = "TextView" + i;
- }
- listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, listValues));
- }
- }
- public class MainActivity extends Activity {
- ElasticListView listView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- listView = (ElasticListView) findViewById(R.id.listview);
- String[] listValues = new String[20];
- for (int i=0;i<listValues.length;i++) {
- listValues[i] = "TextView" + i;
- }
- listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, listValues));
- }
- }
public class FlexibleScrollView extends ScrollView { private Context mContext; private static int mMaxOverDistance = 50; public FlexibleScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; initView(); } public FlexibleScrollView(Context context, AttributeSet attrs) { super(context, attrs); this.mContext = context; initView(); } public FlexibleScrollView(Context context) { super(context); this.mContext = context; initView(); } private void initView() { DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); float density = metrics.density; mMaxOverDistance = (int) (density * mMaxOverDistance); } @Override protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxOverDistance, isTouchEvent); } }通过上面这个类也可以实现弹性效果
二、仿朋友圈背景图片下拉
public class ScrollDampView extends ScrollView { /** 该属性具体参数 怎么控制 未解!!!! */ private static final int LEN = 0xc8; /** 回弹时所用的时间 */ private static final int DURATION = 200; /** 最大Y坐标 其值一般设定为Scroller对应控件的高度 */ private static final int MAX_DY = 200; private Scroller mScroller; /** 阻尼系数 */ private static final float OFFSET_RADIO = 2.5f; private float startY; private int imageViewH; private ImageView imageView; private boolean scrollerType; public ScrollDampView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public ScrollDampView(Context context, AttributeSet attrs) { super(context, attrs); mScroller = new Scroller(context); } public ScrollDampView(Context context) { super(context); } public void setImageView(ImageView imageView) { this.imageView = imageView; } float curY; @Override public boolean dispatchTouchEvent(MotionEvent event) { int action = event.getAction(); if (!mScroller.isFinished()) { return super.onTouchEvent(event); } switch (action) { case MotionEvent.ACTION_DOWN:// 变量赋初始值 imageViewH = imageView.getHeight(); startY = event.getY(); break; case MotionEvent.ACTION_MOVE: if (imageView.isShown()) { float deltaY = (event.getY() - startY ) / OFFSET_RADIO; Log.i("syso", "deltaY: "+deltaY+" imageTop: "+imageView.getTop()); //往下拉 if (deltaY > 0 && deltaY <= imageView.getBottom() + LEN) { android.view.ViewGroup.LayoutParams params = imageView.getLayoutParams(); params.height = (int) (imageViewH + deltaY);// 改变高度 imageView.setLayoutParams(params); } scrollerType = false; } break; case MotionEvent.ACTION_UP: scrollerType = true; // 开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位 // ,即到达坐标为(startX+dx , startY+dy)处 mScroller.startScroll(imageView.getLeft(), imageView.getBottom(), 0 - imageView.getLeft(), imageViewH - imageView.getBottom(), DURATION); invalidate(); break; } return super.dispatchTouchEvent(event); } // //该mScroller针对于imageView的变化 // 被父视图调用,用于必要时候对其子视图的值(mScrollX和mScrollY) // 进行更新。典型的情况如:父视图中某个子视图使用一个Scroller对象来实现滚动操作,会使得此方法被调用。 @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { int x = mScroller.getCurrX(); int y = mScroller.getCurrY();// ImageView的当前Y坐标 imageView.layout(0, 0, x + imageView.getWidth(), y);// 使imageView本身做相应变化 invalidate(); // 滑动还未完成时,手指抬起时,当前y坐标大于其实imageView的高度时 // 设定imageView的布局参数 作用:使除imageView之外的控件做相应变化 if (!mScroller.isFinished() && scrollerType && y > MAX_DY) { android.view.ViewGroup.LayoutParams params = imageView .getLayoutParams(); params.height = y; imageView.setLayoutParams(params); } } } }
布局文件:
<com.example.dampview.ScrollDampView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/dampview" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <!--此处必须设置imageview的scaleType为centerCrop,当然在代码中设置也可以--> <ImageView android:id="@+id/img" android:layout_width="match_parent" android:layout_height="160dp" android:scaleType="centerCrop" android:src="@drawable/image" /> <ImageView android:id="@+id/iv_photo" android:layout_width="64dp" android:layout_height="64dp" android:layout_marginTop="-32dp" android:src="@drawable/ic_launcher" /> </LinearLayout> </com.example.dampview.ScrollDampView>