Android自定义组件系列【5】——进阶实践(1)
接下来几篇文章将对任老师的博文《可下拉的PinnedHeaderExpandableListView的实现》分步骤来详细实现,来学习一下大神的代码并记录一下。
原文出处:http://blog.csdn.net/singwhatiwanna/article/details/25546871
先看一下最终效果:
新建一个activity_main.xml文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.example.testexpandablelistview.ui.StickyLayout
android:id="@+id/sticky_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="0dp"
android:orientation="vertical">
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
android:background="#78a524"
android:orientation="vertical"> </LinearLayout>
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> </LinearLayout>
</com.example.testexpandablelistview.ui.StickyLayout>
</RelativeLayout>
上面的StickyLayout类就是我们自定义的LinearLayout,思路其实很简单,先获取StickyLayout中的header和content两个View,代码如下:
private void initData(){
//使用getIdentifier()方法可以方便的获各应用包下的指定资源ID。
//详细请看:http://blog.sina.com.cn/s/blog_5da93c8f0100zlrx.html
int headerId = getResources().getIdentifier("header", "id", getContext().getPackageName());
int contentId = getResources().getIdentifier("content", "id", getContext().getPackageName());
if(headerId != 0 && contentId != 0){
mHeader = findViewById(headerId);
mContent = findViewById(contentId);
mOriginalHeaderHeight = mHeader.getMeasuredHeight();
mHeaderHeight = mOriginalHeaderHeight;
//是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
Log.d(TAG, "mTouchSlop = " + mTouchSlop);
}else{
throw new NoSuchElementException("Did your view with \"header\" or \"content\" exist?");
}
}
再处理屏幕的监听函数
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int intercepted = 0;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastXIntercept = x;
mLastYIntercept = y;
mLastX = x;
mLastY = y;
intercepted = 0;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if(mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop){
intercepted = 1;
}else if(mGiveUpTouchEventListener != null){
if(mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop){
intercepted = 1;
}
}
break;
case MotionEvent.ACTION_UP:{
intercepted = 0;
mLastXIntercept = mLastYIntercept = 0;
break;
}
default:
break;
}
Log.d(TAG, "intercepted = " + intercepted);
//如果为1则返回true,传递给当前的onTouchEvent。如果为0则返回false,传递给子控件
return intercepted != 0;
}
onInterceptTouchEvent是在ViewGroup里面定义的,用于拦截手势事件,每个手势事件都会先调用onInterceptTouchEvent,如果该方法返回true则拦截到事件,当前的onTouchEvent会触发,如果返回false则传递给子控件。
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
Log.d(TAG, "x=" + x + " y=" + y + " mlastY=" + mLastY);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "mHeaderHeight=" + mHeaderHeight + " deltaY=" + deltaY + " mlastY=" + mLastY);
mHeaderHeight +=deltaY;
setHeaderHeight(mHeaderHeight);
break;
case MotionEvent.ACTION_UP:
int destHeight = 0;
if(mHeaderHeight <= mOriginalHeaderHeight * 0.5){
destHeight = 0;
mStatus = STATUS_COLLAPSED;
}else{
destHeight = mOriginalHeaderHeight;
mStatus = STATUS_EXPANDED;
}
//慢慢滑向终点
this.smoothSetHeaderHeight(mHeaderHeight, destHeight, 500);
break;
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
滑动的时候将事件传递给onTouchEvent
滑动事件中setHeaderHeight改变了上面的header部分的高度,当抬起时会判断是否改变了原始高度的一般,再慢慢滑向终点。
/*
* 改变header的高度
*/
private void setHeaderHeight(int height) {
if(height < 0){
height = 0;
} else if (height > mOriginalHeaderHeight) {
height = mOriginalHeaderHeight;
}
if(mHeaderHeight != height || true){
mHeaderHeight = height;
mHeader.getLayoutParams().height = mHeaderHeight;
mHeader.requestLayout();
}
}
public void smoothSetHeaderHeight(final int from, final int to, long duration) {
final int frameCount = (int) (duration / 1000f * 30) + 1;
final float partation = (to - from) / (float) frameCount;
new Thread("Thread#smoothSetHeaderHeight") {
public void ruan(){
for(int i = 0; i < frameCount; i++) {
final int height;
if(i == frameCount - 1){
height = to;
}else{
height = (int)(from + partation * i);
}
post(new Runnable() { @Override
public void run() {
setHeaderHeight(height);
}
});
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
上面的View.post(Runnable)方法的作用是将Runnable对象添加到UI线程中运行,从而改变header部分的高度。
StickyLayout类的完整代码如下:
package com.example.testexpandablelistview.ui; import java.util.NoSuchElementException; import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.LinearLayout; /**
* 自定义LinearLayout
* @author 转自:http://blog.csdn.net/singwhatiwanna/article/details/25546871
*
*/
public class StickyLayout extends LinearLayout{ private static final String TAG = "StickyLayout"; public interface OnGiveUpTouchEnventListener{
public boolean giveUpTouchEvent(MotionEvent event);
} private View mHeader; //上面部分,下面成为Header
private View mContent; //下面部分
private OnGiveUpTouchEnventListener mGiveUpTouchEventListener; private int mTouchSlop; //移动的距离 //header的高度 单位:px
private int mOriginalHeaderHeight; //Header部分的原始高度
private int mHeaderHeight; //Header部分现在的实际高度(随着手势滑动会变化) private int mStatus = STATUS_EXPANDED; //当前的状态
public static final int STATUS_EXPANDED = 1; //展开状态
public static final int STATUS_COLLAPSED = 2; //闭合状态 //分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0; //分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0; /*
* 构造函数1
*/
public StickyLayout(Context context){
super(context);
} /*
* 构造函数2
*/
public StickyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
} /* 构造函数3
* TargetApi 标签的作用是使高版本的api代码在低版本sdk不报错
*/ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public StickyLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
} /**
* onWindowFocusChanged方法用于监听一个activity是否加载完毕,Activity生命周期中,
* onStart, onResume, onCreate都不是真正visible的时间点,真正的visible时间点是onWindowFocusChanged()函数被执行时。
*/
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
//如果是activity加载完毕,mHeader和mContent未被初始化,则执行初始化方法。
if(hasWindowFocus && (mHeader == null || mContent == null)){
initData();
}
} private void initData(){
//使用getIdentifier()方法可以方便的获各应用包下的指定资源ID。
//详细请看:http://blog.sina.com.cn/s/blog_5da93c8f0100zlrx.html
int headerId = getResources().getIdentifier("header", "id", getContext().getPackageName());
int contentId = getResources().getIdentifier("content", "id", getContext().getPackageName());
if(headerId != 0 && contentId != 0){
mHeader = findViewById(headerId);
mContent = findViewById(contentId);
mOriginalHeaderHeight = mHeader.getMeasuredHeight();
mHeaderHeight = mOriginalHeaderHeight;
//是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
Log.d(TAG, "mTouchSlop = " + mTouchSlop);
}else{
throw new NoSuchElementException("Did your view with \"header\" or \"content\" exist?");
}
} @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int intercepted = 0;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastXIntercept = x;
mLastYIntercept = y;
mLastX = x;
mLastY = y;
intercepted = 0;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if(mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop){
intercepted = 1;
}else if(mGiveUpTouchEventListener != null){
if(mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop){
intercepted = 1;
}
}
break;
case MotionEvent.ACTION_UP:{
intercepted = 0;
mLastXIntercept = mLastYIntercept = 0;
break;
}
default:
break;
}
Log.d(TAG, "intercepted = " + intercepted);
//如果为1则返回true,传递给当前的onTouchEvent。如果为0则返回false,传递给子控件
return intercepted != 0;
} @Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
Log.d(TAG, "x=" + x + " y=" + y + " mlastY=" + mLastY);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
Log.d(TAG, "mHeaderHeight=" + mHeaderHeight + " deltaY=" + deltaY + " mlastY=" + mLastY);
mHeaderHeight +=deltaY;
setHeaderHeight(mHeaderHeight);
break;
case MotionEvent.ACTION_UP:
int destHeight = 0;
if(mHeaderHeight <= mOriginalHeaderHeight * 0.5){
destHeight = 0;
mStatus = STATUS_COLLAPSED;
}else{
destHeight = mOriginalHeaderHeight;
mStatus = STATUS_EXPANDED;
}
//慢慢滑向终点
this.smoothSetHeaderHeight(mHeaderHeight, destHeight, 500);
break;
default:
break;
}
mLastX = x;
mLastY = y;
return true;
} public void smoothSetHeaderHeight(final int from, final int to, long duration) {
final int frameCount = (int) (duration / 1000f * 30) + 1;
final float partation = (to - from) / (float) frameCount;
new Thread("Thread#smoothSetHeaderHeight") {
public void ruan(){
for(int i = 0; i < frameCount; i++) {
final int height;
if(i == frameCount - 1){
height = to;
}else{
height = (int)(from + partation * i);
}
post(new Runnable() { @Override
public void run() {
setHeaderHeight(height);
}
});
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
} /*
* 改变header的高度
*/
private void setHeaderHeight(int height) {
if(height < 0){
height = 0;
} else if (height > mOriginalHeaderHeight) {
height = mOriginalHeaderHeight;
}
if(mHeaderHeight != height || true){
mHeaderHeight = height;
mHeader.getLayoutParams().height = mHeaderHeight;
mHeader.requestLayout();
}
}
}
MainActivity.java
package com.example.testexpandablelistview; import android.app.Activity;
import android.os.Bundle; import com.example.testexpandablelistview.ui.StickyLayout; public class MainActivity extends Activity{ @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
运行效果:
原文地址:http://blog.csdn.net/singwhatiwanna/article/details/25546871
Android自定义组件系列【5】——进阶实践(1)的更多相关文章
- Android自定义组件系列【7】——进阶实践(4)
上一篇<Android自定义组件系列[6]--进阶实践(3)>中补充了关于Android中事件分发的过程知识,这一篇我们接着来分析任老师的<可下拉的PinnedHeaderExpan ...
- Android自定义组件系列【6】——进阶实践(3)
上一篇<Android自定义组件系列[5]--进阶实践(2)>继续对任老师的<可下拉的PinnedHeaderExpandableListView的实现>进行了分析,这一篇计划 ...
- Android自定义组件系列【5】——进阶实践(2)
上一篇<Android自定义组件系列[5]--进阶实践(1)>中对任老师的<可下拉的PinnedHeaderExpandableListView的实现>前一部分进行了实现,这一 ...
- Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动
在上一篇文章<Android自定义组件系列[3]--自定义ViewGroup实现侧滑>中实现了仿Facebook和人人网的侧滑效果,这一篇我们将接着上一篇来实现双面滑动的效果. 1.布局示 ...
- Android自定义组件系列【3】——自定义ViewGroup实现侧滑
有关自定义ViewGroup的文章已经很多了,我为什么写这篇文章,对于初学者或者对自定义组件比较生疏的朋友虽然可以拿来主义的用了,但是要一步一步的实现和了解其中的过程和原理才能真真脱离别人的代码,举一 ...
- Android自定义组件系列【12】——非UI线程绘图SurfaceView
一.SurfaceView的介绍 在前面我们已经会自定义View,使用canvas绘图,但是View的绘图机制存在一些缺陷. 1.View缺乏双缓冲机制. 2.程序必须重绘整个View上显示的图片,比 ...
- Android自定义组件系列【1】——自定义View及ViewGroup
View类是ViewGroup的父类,ViewGroup具有View的所有特性,ViewGroup主要用来充当View的容器,将其中的View作为自己孩子,并对其进行管理,当然孩子也可以是ViewGr ...
- Android自定义组件系列【17】——教你如何高仿微信录音Toast
一.Toast介绍 平时我们在Android开发中会经常用到一个叫Toast的东西,官方解释如下 A toast is a view containing a quick little message ...
- Android自定义组件系列【15】——四个方向滑动的菜单实现
今天无意中实现了一个四个方向滑动的菜单,感觉挺好玩,滑动起来很顺手,既然已经做出来了就贴出来让大家也玩弄一下. 一.效果演示 (说明:目前没有安装Android模拟器,制作的动态图片太卡了,就贴一下静 ...
随机推荐
- js---25桥模式
桥接模式是一种既能把两个对象连接在一起,又能避免二者间的强耦合的方法.通过“桥”把彼此联系起来,同时又允许他们各自独立变化. 主要作用表现为将抽象与其实现隔离开来,以便二者独立化. <!DOCT ...
- [BZOJ2238]Mst 最小生成树+树链剖分/并查集
链接 题解 先构建出最小生成树,如果删的是非树边,直接输出答案 否则问题转化为,把该边删掉后剩下两个联通块,两个端点分别在两个块内的最小边权,LCT可以维护 不妨换一种思考方向:考虑一条非树边可以代替 ...
- Codefroces 760 B. Frodo and pillows
B. Frodo and pillows time limit per test 1 second memory limit per test 256 megabytes input standard ...
- 字符设备驱动-------Linux异常处理体系结构
裸机中断流程 外部触发 CPU 发生中断, 强制的跳到异常向量处 跳转到具体函数 保存被中断处的现场(各种寄存器的值) 执行中断处理函数,处理具体任务 恢复被中断的现场 Linux处理异常流程 异常发 ...
- 数值溢出(arithmetic overflow)问题与解决方案
0. 典型场景 两数相加(乘法).两数相减.一个数的阶乘,一个数的幂,这些统统可能造成数值的溢出: 避免数值溢出的方法: 当把一个计算出的很大的数赋值给一个 int(2^31-1)类型变量存储时,一般 ...
- android studio执行 Information:Gradle tasks [:app:assembleDebug]失败处理
Error:Execution failed for task ‘:app:mergeDebugResources’. > Some file crunching failed, see log ...
- Django模板变量,过滤器和静态文件引用
模版路径查找 首先去settings.py里面找TEMPLATES ,在TEMPLATES下面找DIRS,找到就返回,没找到就继续往下,如果APP_DIRS设置为为Ture,那么就会到上面 INSTA ...
- Redis的高级应用-安全性和主从复制
Redis的服务器命令和键值命令(String,Hash,List,Set,Zset)相对简单,只需查看文档即可. 文档地址: http://www.runoob.com/redis/redis-tu ...
- 洛谷 P1601 A+B Problem(高精)
P1601 A+B Problem(高精) 题目背景 无 题目描述 高精度加法,x相当于a+b problem,[b][color=red]不用考虑负数[/color][/b] 输入输出格式 输入格式 ...
- 彩票案例-frame,center和bounds属性
控件的属性: 二.frame.center和bounds属性 " 在iOS中,每一个控件都是继承于UIView的.都会有视图的属性存在,控制这个视图的位置就有Frame和Bounds两个属性 ...