Android开发之自定义View专题(四):自定义ViewGroup - 新闻资讯 - 云南小程序开发|云南软件开发|云南网站建设-昆明葵宇信息科技有限公司

159-8711-8523

云南网建设/小程序开发/软件开发

知识

不管是网站,软件还是小程序,都要直接或间接能为您产生价值,我们在追求其视觉表现的同时,更侧重于功能的便捷,营销的便利,运营的高效,让网站成为营销工具,让软件能切实提升企业内部管理水平和效率。优秀的程序为后期升级提供便捷的支持!

您当前位置>首页 » 新闻资讯 » 技术分享 >

Android开发之自定义View专题(四):自定义ViewGroup

发表时间:2020-10-19

发布人:葵宇科技

浏览次数:27


     有时刻,我们会有如许的需求,一个activity琅绫擎须要有两个或者多个界面切换,就像Viewpager那样。然则在这些界面琅绫擎又须要可以或许有listView,gridview等组件。如不蚜鲚向的,似乎还好,没什么竽暌拱响,那么如不雅是横向的,那么就会出工作。因为Viewpager会拦截触摸事宜。而如不雅将Viewpager的触摸事沂攀拦截掉落给琅绫擎的子控件,那么Viewpager又不克不及响应滑动事宜了。那么若何又能让界面之间可以或许往返切换,又能让琅绫擎的子控件的触摸事宜也能毫无影响的响应呢,这个时刻,我们须要自定义一个Viewgroup,重写琅绫擎的触摸拦截办法即可。
     博主自定义的ViewGroup类似于SlideMenu,包含两个界面的往返切换,博主特意放了一个可以横向滑动item的listView组件在的个界面实验,这个listView控件也是博主之前大年夜网上找出来竽暌姑的,还不错的一个控件。具体看效不雅图:
[img]http://img.blog.csdn.net/20150105195005125?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmljdG9yZnJlZWRvbQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center[img]http://img.blog.csdn.net/20150105195027482?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdmljdG9yZnJlZWRvbQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center
好了,惯例子,完全项面前目今载地址:
http://download.csdn.net/detail/victorfreedom/8329667
有兴趣的同窗可以下载下来研究进修。
自定义ViewGroup具体代码:
package com.freedom.slideviewgroup.ui;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.widget.Scroller;

import com.freedom.slideviewgroup.FreedomApplication;
import com.freedom.slideviewgroup.utils.DptoPxUtil;

/**
 * @ClassName: SlideMenu
 * @author victor_freedom (x_freedom_reddevil@126.com)
 * @createddate 2015-1-5 下昼8:00:36
 * @Description: TODO
 */

public class SlideMenu extends ViewGroup {

	private Context mContext;

	// 默认第一个
	private int currentScreen = 0; // 当前屏
	// 移动控制者
	private Scroller mScroller = null;
	// 断定是否可以移动
	private boolean canScroll = false;
	// 是否拦截点击事宜,false表示拦截,true表示将点击事宜传递给子控件
	private boolean toChild = false;
	// 处理触摸事宜的移动速度标准
	public static int SNAP_VELOCITY = 600;
	// 触发move的最小滑动距离
	private int mTouchSlop = 0;
	// 最后一点的X坐标
	private float mLastionMotionX = 0;
	// 处理触摸的速度
	private VelocityTracker mVelocityTracker = null;
	// 阁下子控件的监听器
	private LeftListener leftListener;
	private RightListener rightListener;
	// 触摸状况
	private static final int TOUCH_STATE_REST = 0;
	private static final int TOUCH_STATE_SCROLLING = 1;
	private int mTouchState = TOUCH_STATE_REST;
	// 响应触摸事宜的边距剖断距离(这个根据自定义响应)
	public static int TOHCH_LEFT = 140;
	public static int TOHCH_RIGHT = FreedomApplication.mScreenWidth;
	public static final String TAG = "SlideMenu";

	public SlideMenu(Context context) {
		super(context);
		mContext = context;
		init();
	}

	public SlideMenu(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		init();
	}

	/**
	 * @Title: init
	 * @Description: 初始化滑动相干的器械
	 * @throws
	 */
	private void init() {
		mScroller = new Scroller(mContext, new AccelerateInterpolator());
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
	}

	/**
	 * @Title: onMeasure
	 * @Description: 设定viewGroup大年夜小
	 * @param widthMeasureSpec
	 * @param heightMeasureSpec
	 * @throws
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		int width = MeasureSpec.getSize(widthMeasureSpec);
		int height = MeasureSpec.getSize(heightMeasureSpec);
		setMeasuredDimension(width, height);
		for (int i = 0; i < getChildCount(); i++) {
			getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
		}
	}

	/**
	 * @Title: onLayout
	 * @Description: 设置子控件的分布地位
	 * @param changed
	 * @param l
	 *            left
	 * @param t
	 *            top
	 * @param r
	 *            right
	 * @param b
	 *            bottom
	 * @throws
	 */
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int startLeft = 0; // 每个子视图的肇端构造坐标
		int childCount = getChildCount();

		for (int i = 0; i < childCount; i++) {
			View child = getChildAt(i);
			child.layout(startLeft, 0, startLeft + getWidth(), getHeight());
			startLeft = startLeft + getWidth(); // 校准每个子View的肇端构造地位
		}
	}

	/**
	 * @Title: onInterceptTouchEvent
	 * @Description:触摸事沂攀拦截剖断
	 * @param ev
	 * @return
	 * @throws
	 */
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		final int action = ev.getAction();
		// 如不雅当前正在滑动状况,则拦截事宜
		if ((action == MotionEvent.ACTION_MOVE)
				&& (mTouchState != TOUCH_STATE_REST)) {
			return true;
		}

		final float x = ev.getX();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			mLastionMotionX = x;
			// 断定当缁ご态
			mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
					: TOUCH_STATE_SCROLLING;
			// 断定是否可以或许响应滑动事宜
			if ((ev.getX() < DptoPxUtil.dip2px(mContext, TOHCH_LEFT) && currentScreen == 1)
					|| (ev.getX() > TOHCH_RIGHT
							- DptoPxUtil.dip2px(mContext, 200) && currentScreen == 0)) {
				canScroll = true;
				toChild = false;
				return super.onInterceptTouchEvent(ev);
			} else {
				// 如不雅不克不及则不拦截事宜
				canScroll = false;
				toChild = true;
				return false;
			}
		case MotionEvent.ACTION_MOVE:
			if (toChild) {
				return false;
			}
			final int differentX = (int) Math.abs(mLastionMotionX - x);
			// 跨越了最小滑动距离,并且没有传递事宜给子控件,则更改状况
			if (differentX > mTouchSlop) {
				mTouchState = TOUCH_STATE_SCROLLING;
			}
			break;
		case MotionEvent.ACTION_UP:
			if (toChild) {
				return false;
			}
			mTouchState = TOUCH_STATE_REST;
			break;
		}
		return super.onInterceptTouchEvent(ev);
	}

	/**
	 * @Title: onTouchEvent
	 * @Description: 触摸事宜响应
	 * @param event
	 * @return
	 * @throws
	 */
	public boolean onTouchEvent(MotionEvent event) {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		// 获取移动的速度
		mVelocityTracker.addMovement(event);
		super.onTouchEvent(event);

		// 手指地位地点
		float x = event.getX();

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// 如不雅屏幕的动画还没停止,你就按下了,我们就停止该动画
			if (mScroller != null) {
				if (!mScroller.isFinished()) {
					mScroller.abortAnimation();
				}
			}
			mLastionMotionX = x;
			break;
		case MotionEvent.ACTION_MOVE:
			if (canScroll) {
				int detaX = (int) (mLastionMotionX - x);
				mLastionMotionX = x;
				// 移动距离
				scrollBy(detaX, 0);

			}
			break;
		case MotionEvent.ACTION_UP:
			final VelocityTracker velocityTracker = mVelocityTracker;
			velocityTracker.computeCurrentVelocity(1000);
			int velocityX = (int) velocityTracker.getXVelocity();
			// 滑动速度达到了一个标准(快速向右滑屏,返回上一个屏幕) 立时进行切屏处理
			if (velocityX > SNAP_VELOCITY && currentScreen > 0 && canScroll) {
				changedScreen(currentScreen - 1);
			}
			// 快速向左滑屏,返回下一屏幕)
			else if (velocityX < -SNAP_VELOCITY
					&& currentScreen < (getChildCount() - 1) && canScroll) {
				changedScreen(currentScreen + 1);
			}
			// 以上为快速移动的 ,强迫切换屏幕
			else {
				// 如不雅移动迟缓,那么先断定是保存在本屏幕照样到下一屏幕
				snapToDestination();
			}

			if (mVelocityTracker != null) {
				mVelocityTracker.recycle();
				mVelocityTracker = null;
			}

			mTouchState = TOUCH_STATE_REST;

			break;
		case MotionEvent.ACTION_CANCEL:
			mTouchState = TOUCH_STATE_REST;
			break;
		}

		return super.onInterceptTouchEvent(event);
	}

	@Override
	public void computeScroll() {
		if (mScroller.computeScrollOffset()) {// 如不雅返回true,则代表正在模仿数据,false表示已经停止模仿数据
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());// 更新偏移量
			postInvalidate();
		}
	}

	/**
	 * @Title: startMove
	 * @Description: 这是大年夜第一个屏幕跳转到第二个屏幕的快捷办法
	 * @throws
	 */
	public void startMove() {
		if (currentScreen == 1) {
			return;
		}
		if (currentScreen == 0 && rightListener != null) {
			new Thread(new Runnable() {

				@Override
				public void run() {
					rightListener.postNotifyDataChange();
				}
			}).start();

		}
		currentScreen++;
		mScroller.startScroll((currentScreen - 1) * getWidth(), 0, getWidth(),
				0, 600);
		// 刷新界面
		invalidate();// invalidate -> drawChild -> child.draw -> computeScroll

	}

	/**
	 * @Title: startMoves
	 * @Description: 跳转到第一个屏幕
	 * @throws
	 */
	public void startMoves() {
		changedScreen(0);
	}

	/**
	 * @Title: snapToDestination
	 * @Description: 当迟缓移动的时刻,断定跳转屏幕
	 * @throws
	 */
	private void snapToDestination() {
		int destScreen = (getScrollX() + getWidth() / 3) / getWidth();
		changedScreen(destScreen);
	}

	/**
	 * @Title: changedScreen
	 * @Description: 跳转屏幕
	 * @param whichScreen
	 * @throws
	 */
	private void changedScreen(int whichScreen) {
		currentScreen = whichScreen;
		if (currentScreen > getChildCount() - 1) {
			currentScreen = getChildCount() - 1;
		}
		if (currentScreen == 0 && leftListener != null) {
			leftListener.notifyDataChange();
		}
		if (currentScreen == 1 && rightListener != null) {
			rightListener.notifyDataChange();
		}
		// getScrollX获得的是当前视图相对于父控件的偏移量。初始值是0,
		int dx = currentScreen * getWidth() - getScrollX();
		// dx为正值瓯,屏幕向右滑动,dx为负值瓯,屏幕向左滑动
		mScroller.startScroll(getScrollX(), 0, dx, 0, 600);
		postInvalidate();

	}

	public interface LeftListener {
		public void notifyDataChange();
	}

	public interface RightListener {
		public void notifyDataChange();

		public void postNotifyDataChange();
	}

	public void setLeftListener(LeftListener leftListener) {
		this.leftListener = leftListener;
	}

	public void setRightListener(RightListener rightListener) {
		this.rightListener = rightListener;
	}

}

自此自定义View专题已经讲解完毕,信赖所有人都对自定义View有了一个初步的熟悉,根本上就是那么几个步调。而自定义ViewGroup相对于自定义View多了一个步调在于要重写onLayout办法来摆放包含在琅绫擎的子控件,其余的,都差不多。
     
好了,惯例子,完全项面前目今载地址:
http://download.csdn.net/detail/victorfreedom/8329667
有兴趣的同窗可以下载下来研究进修。
自定义ViewGroup具体代码:

相关案例查看更多