Android自己定义组件系列【5】——高级实践(1)
在接下来的几篇文章将任老师的博文《您可以下拉PinnedHeaderExpandableListView实现》骤来具体实现。来学习一下大神的代码并记录一下。
原文出处:http://blog.csdn.net/singwhatiwanna/article/details/25546871
先看一下终于效果:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2luZ3doYXRpd2FubmE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
新建一个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,思路事实上非常easy,先获取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中事件分发的过程知识.这一篇我们接着来分析任老师的<可下拉的PinnedHeaderExpa ...
- 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自己定义组件系列【1】——自己定义View及ViewGroup
View类是ViewGroup的父类,ViewGroup具有View的全部特性.ViewGroup主要用来充当View的容器.将当中的View作为自己孩子,并对其进行管理.当然孩子也能够是ViewGr ...
- Android自己定义组件系列【2】——Scroller类
在上一篇中介绍了View类的scrollTo和scrollBy两个方法,对这两个方法不太了解的朋友能够先看<自己定义View及ViewGroup> scrollTo和scrollBy尽管实 ...
- Android自己定义组件系列【9】——Canvas绘制折线图
有时候我们在项目中会遇到使用折线图等图形,Android的开源项目中为我们提供了非常多插件,可是非常多时候我们须要依据详细项目自己定义这些图表,这一篇文章我们一起来看看怎样在Android中使用Can ...
- Android自己定义组件系列【8】——面膜文字动画
我们掩盖文字动画Flash中非经货共同体共同,由于Android应用程序开发人员做你想要做这个动画在应用程序中去?本文中,我们看的是如何自己的定义ImageView来实现让一张文字图片实现文字的遮罩闪 ...
随机推荐
- 通过加载Kernel32来动态判断 当前操作系统32bit还是64bit
工作原理:通过加载Kernel32来获取IsWow64Process 函数然后通过函数的地址操作,执行函数的操作. 在程序中只要我们获取了一个函数的地址,就可以找到正确的方法执行这个函数. 但是这种方 ...
- QUIC简单介绍
QUIC,即Quick UDP Internet Connection,类似于SPDY,相同也是由Google公司在现有已存协议之上进行了扩展设计,而旨在降低网络延迟.之前我曾介绍过SPDY的相关信息 ...
- jenkins 通过批处理自动构建 非标准项目
之前介绍了java和vs2010的项目构建,这些都是比较常见的,所以都用专门的工具.但但难免会遇到一些不常见的项目,下面介绍通过批处理进行构建,并用jenkins调用.我们这里使用plc语言,没有标准 ...
- Just learn how to use the JNI
JNITestProject Just learn how to use the JNI Refer : 1. ant usage http://lmbj.net/blog/ant-build-and ...
- BGP拓扑正确配置
R1的配置 ----------------------------------------------------------------------------- sysname RT1# sup ...
- Windows 8.1下 MySQL绿色版安装配置与使用
原文:Windows 8.1下 MySQL绿色版安装配置与使用 Mysql-5.6.17-winx64操作步骤: 一.安装MySQL数据库 1.下载. 下载地址:http://downloads.my ...
- [置顶] ※数据结构※→☆线性表结构(stack)☆============栈 序列表结构(stack sequence)(六)
栈(stack)在计算机科学中是限定仅在表尾进行插入或删除操作的线性表.栈是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据.栈 ...
- Invalid embedded descriptor for ".proto".Dependencies passed (Protobufer)解决办法
前言 之前开发的时候,发现居然出现了Dependencies passed to FileDescriptor.buildFrom() don't match those listed in the ...
- MySQL在一台db服务器上面如何启动多个实例
安装过程省略过,源码安装请参考http://write.blog.csdn.net/postlist/1609043/all 整理自己的文档,发现以前做的例子,share下,欢迎大家提出改进意见. 一 ...
- HTML5:footer定位(底部+居中)的探讨+div图片居中问题
初学HTML+CSS布局,尝试自己写一个百度首页,可是footer的定位遇到麻烦而且百度没有好的解决方法,在此记录下逐步的过程.记录之,备忘. 初学,解决方法难免出现不妥之处,也请看到这篇文章的前辈指 ...