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


原文地址:https://www.cnblogs.com/lanzhi/p/6468981.html