接下来几篇文章将对任老师的博文《可下拉的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