最近一直在研究自定义控件,一般大致分为三种情况:自绘控件,组合控件,继承控件。接下来我们来看下继承控件。在此借鉴一位博主的文章,补充粘性的实现效果,并且加注自己的一些理解。大家也可以查看原文博客。android之自定义viewGroup仿scrollView详解
直接上代码,注释的比较详细。可以通过Log的信息来观察下滑动时候坐标的变化,加深理解。 public class MyScrollViewGroup extends ViewGroup {
private Context mContext;
private int mScreenHeight;
private int totalHeight;
private Scroller mScroller; public MyScrollViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
} public MyScrollViewGroup(Context context) {
super(context);
init(context);
} private void init(Context context) {
mContext = context;
mScreenHeight = getScreenSize(mContext).heightPixels;
mScroller = new Scroller(mContext);
} /***
* 获取真实的宽高 比如200px
*
* @param widthMeasureSpec
* @return
*/
public int measureRealWidth(int widthMeasureSpec) {
int result = 200;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int realWidth = MeasureSpec.getSize(widthMeasureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY:
//MeasureSpec.EXACTLY:精确值模式: 控件的layout_width或layout_heiht指定为具体值,比如200dp,或者指定为match_parent(占据父view的大小),系统返回的是这个模式
result = realWidth;
Log.d(TAG, "EXACTLY result " + result);
break;
case MeasureSpec.AT_MOST:
// MeasureSpec.AT_MOST: 最大值模式,控件的layout_width或layout_heiht指定为wrap_content时,控件大小一般随着控件的子控件或内容的变化而变化,此时控件的尺寸不能超过父控件
result = Math.max(result, realWidth);
Log.d(TAG, "AT_MOST result " + result);
break;
case MeasureSpec.UNSPECIFIED:
// MeasureSpec.UNSPECIFIED:不指定其大小测量模式,通常在绘制定义view的时候才会使用,即多大由开发者在onDraw()的时候指定大小
result = realWidth;
Log.d(TAG, "UNSPECIFIED result " + result);
break;
}
return result;
} /***
* @param widthMeasureSpec 系统测量的宽 一共是32位的 高2位代表模式 低30位表示大小
* @param heightMeasureSpec 系统测量的高 一共是32位的 高2位代表模式 低30位表示大小
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(TAG, "widthMeasureSpec " + widthMeasureSpec);
Log.d(TAG, "heightMeasureSpec " + heightMeasureSpec);
/***自身宽*/
int measureSelfWidth = measureRealWidth(widthMeasureSpec);
int measureSelfHeight = MeasureSpec.getSize(heightMeasureSpec);
Log.d(TAG, "widthMeasure " + measureSelfWidth);
Log.d(TAG, "widthMode " + MeasureSpec.getMode(widthMeasureSpec));
Log.d(TAG, "heightMeasure " + MeasureSpec.getSize(heightMeasureSpec));
Log.d(TAG, "heightMode " + MeasureSpec.getMode(heightMeasureSpec)); int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
//设置viewGroup的宽高,也可以在onlayout中通过layoutParams设置
totalHeight = getScreenSize(mContext).heightPixels * childCount;
setMeasuredDimension(measureSelfWidth, totalHeight);
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.d(TAG, "onLayout left " + l);
Log.d(TAG, "onLayout top " + t);
Log.d(TAG, "onLayout right " + r);
Log.d(TAG, "onLayout bottom " + b);
Log.d(TAG, "onLayout heightPixels " + getScreenSize(mContext).heightPixels);
int childCount = getChildCount(); for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
childView.layout(l, i * mScreenHeight, r, (i + 1) * mScreenHeight);
}
} private float lastDownY;
private float mScrollStart;
private float mScrollEnd; @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastDownY = event.getY();
mScrollStart = getScrollY();
Log.d(TAG, "totalHeight = " + totalHeight);
break;
case MotionEvent.ACTION_MOVE:
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
float currentY = event.getY();
float dy;
dy = lastDownY - currentY;
Log.d("test", "dy = " + dy);
Log.d("test", "getScrollY() = " + getScrollY());
Log.d("test", "getHeight() = " + getHeight());
Log.d("test", "mScreenHeight() = " + mScreenHeight);
Log.d("test", "getHeight() - mScreenHeight = " + (getHeight() - mScreenHeight));
if (getScrollY() < 0) {
dy = 0;
//最顶端,超过0时,不再下拉,要是不设置这个,getScrollY一直是负数
} else if (getScrollY() > getHeight() - mScreenHeight) {
dy = 0;
//滑到最底端时,不再滑动,要是不设置这个,getScrollY一直是大于getHeight() - mScreenHeight的数,无法再滑动
}
scrollBy(0, (int) dy);
//不断的设置Y,在滑动的时候子view就会比较顺畅
lastDownY = event.getY();
break;
case MotionEvent.ACTION_UP:
mScrollEnd = getScrollY();
int dScrollY = (int) (mScrollEnd - mScrollStart);
Log.d("test", "dScrollY = " + dScrollY);
//此处实现的是根据滑动的距离来实现滚动
// if (mScrollEnd < 0) {// 最顶端:手指向下滑动,回到初始位置
// Log.d(TAG, "mScrollEnd < 0" + dScrollY);
// mScroller.startScroll(0, getScrollY(), 0, -getScrollY());
// } else if (mScrollEnd > getHeight() - mScreenHeight) {//已经到最底端,手指向上滑动回到底部位置
// Log.d(TAG, "getHeight() - mScreenHeight - (int) mScrollEnd " + (getHeight() - mScreenHeight - (int) mScrollEnd));
// mScroller.startScroll(0, getScrollY(), 0, getHeight() - mScreenHeight - (int) mScrollEnd);
// }
//此处实现的是根据设定的距离,来实现粘性滑动的效果
if (dScrollY > 0) {
//向上滑动dScrollY为正值
if (dScrollY < mScreenHeight / 3) {
mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
} else {
mScroller.startScroll(0, getScrollY(), 0, (mScreenHeight - dScrollY));
}
} else {
//向下滑动dScrollY为负值
if (-dScrollY < mScreenHeight / 3) {
mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
} else {
mScroller.startScroll(0, getScrollY(), 0, (-mScreenHeight - dScrollY));
}
}
break;
}
postInvalidate();// 重绘执行computeScroll()
return true;//需要返回true否则down后无法执行move和up操作
} /**
* Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性,但它并不是UI,也不是滑动辅助UI运动,反而是单纯地为滑动提供计算
* 需要invalidate()之后才会调用,这个方法在onDraw()中调用
*/
@Override
public void computeScroll() {
super.computeScroll();
Log.d(TAG, "mScroller.getCurrY() " + mScroller.getCurrY());
if (mScroller.computeScrollOffset()) {//是否已经滚动完成
scrollTo(0, mScroller.getCurrY());//获取当前值,startScroll()初始化后,调用就能获取区间值
postInvalidate();
}
} /**
* 获取屏幕大小,这个可以用一个常量不用每次都获取
*
* @param context
* @return
*/
public static DisplayMetrics getScreenSize(Context context) {
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
return metrics;
} } 下面附上两种运行效果,比较下不同之处。

android之自定义viewGroup仿scrollView的两种实现(滚动跟粘性)的更多相关文章

  1. [Android] Android ViewPager 中加载 Fragment的两种方式 方式(二)

    接上文: https://www.cnblogs.com/wukong1688/p/10693338.html Android ViewPager 中加载 Fragmenet的两种方式 方式(一) 二 ...

  2. [Android] Android ViewPager 中加载 Fragment的两种方式 方式(一)

    Android ViewPager 中加载 Fragmenet的两种方式 一.当fragment里面的内容较少时,直接 使用fragment xml布局文件填充 文件总数 布局文件:view_one. ...

  3. 怎样在Android开发中FPS游戏实现的两种方式比较

    怎样在Android开发中FPS游戏实现的两种方式比较 如何用Android平台开发FPS游戏,其实现过程有哪些方法,这些方法又有哪些不同的地方呢?首先让我们先了解下什么是FPS 英文名:FPS (F ...

  4. android带有文字的图片按钮的两种实现方式

    android带有文字的图片按钮的两种实现方式 1). TextView对Button用相对布局,这要要求按钮的背景图片要留下空白位置给文字.这种方式开发比较简单,适合做一些风格一致的Button. ...

  5. Android中H5和Native交互的两种方式

    Android中H5和Native交互的两种方式:http://www.jianshu.com/p/bcb5d8582d92 注意事项: 1.android给h5页面注入一个对象(WZApp),这个对 ...

  6. Android之自定义ViewGroup

    概述 在写代码之前,我必须得问几个问题: 1.ViewGroup的职责是啥? ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属性, ...

  7. Android 进阶自定义 ViewGroup 自定义布局

    前言 在我们的实际应用中, 经常需要用到自定义控件,比如自定义圆形头像,自定义计步器等等.但有时我们不仅需要自定义控件,举个例子,FloatingActionButton 大家都很常用,所以大家也很经 ...

  8. iOS/Android网络消息推送的实现两种方法

    移动时代,用户为王,而每个APP拥有的活跃用户量(Active Users),决定了其价值. 消息推送成为了不可或缺的活跃唤起工具. 目前消息推送有如下两种途径: 1.iOS传统方式: 通过Apple ...

  9. Android双击返回键退出Activity的两种方法

    在开发应用程序的时候,有一种功能是非常常用到的,那就是迅速双击返回按钮,然后实现退出Activity的功能.本人在网上看了很多资料代码,总结起来,主要有两种比较好的方式.一种是开线程延时执行,一种是记 ...

随机推荐

  1. 【基础】Qt SCXML Calculator QML Example

    Qt SCXML Calculator QML Example 这个系统自带的例子原本主要是用来说明SCXML机制的,但是由于计算器的经典和简洁,我认为用来练习QML非常合适,原本的例子还有一些问题, ...

  2. CentOS7下安装ELK(nginx 、elasticsearch-5.1.1、logstash-5.1.1、kibana-5.1.1)

    nginx: #直接yum安装: [root@elk-node1 ~]# yum install nginx -y 官方文档:http://nginx.org/en/docs/http/ngx_htt ...

  3. linux 压缩、解压、zip/unzip/jar

    网上很多人说用jar命令解压,但jar命令解压时不能指定目录,推荐使用unzip解压war包. unzip -d 指定目录 [root@oracle upload]# unzip -oq common ...

  4. 阿里云Maven仓库地址setting配置

    <?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...

  5. JAVA获取树形结构

    package com.nnmzkj.common.dto; import lombok.Data; import java.io.Serializable;import java.util.Arra ...

  6. Automl基于超大数据下的数据分发方案探讨

    先定义几个关键字: 任务:用户一次上传的数据集并发起的automl任务,比如一次ocr任务,一次图像分类任务. 模型:一次任务中,需要运行的多个模型,比如ocr任务,需要ctpn模型,需要crnn模型 ...

  7. python初级(302) 7 列表(二)冒泡排序

    一.复习: 1.如何创建一个空列表,如何创建一个有数据的列表 2.列表可以包含的内容 3.从列表中获取元素和修改元素的方法 4.列表的分片 5.增加元素和删除元素 6.选择排序的算法: 一堆数据,每次 ...

  8. python - logging.basicConfig format参数无效

    有这么一段python代码 import threading import time import requests from decimal import Decimal, ROUND_DOWN i ...

  9. Python - importlib 模块

    importlib 模块可以根据字符串来导入相应的模块 目录结构: 在根目录下创建 importlib_test.py 和 aaa/bbb.py bbb.py: class Person(object ...

  10. EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器解决方案之点播分享

    背景介绍 EasyDSS流媒体服务器软件,提供一站式的视频上传.转码.点播.直播.时移回放等服务,极大地简化了开发和集成的工作.其中,点播功能主要包含:上传.转码.分发.直播功能,主要包含:直播.录像 ...