• 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


  • 相关阅读:
    AT2667-[AGC017D]Game on Tree【SG函数】
    P5163-WD与地图【tarjan,整体二分,线段树合并】
    P4258-[WC2016]挑战NPC【带花树】
    P7099-[yLOI2020]灼【数学期望,结论】
    P5056-[模板]插头dp
    P2012-拯救世界2【EGF】
    [CSP校内集训]替换游戏(tarjan+离散化)
    4.24作业
    【题解】考分鄙视
    【题解】鱼塘钓鱼
  • 原文地址:https://www.cnblogs.com/lanzhi/p/6468981.html
Copyright © 2020-2023  润新知