文档章节

自定义属性和视图(仿ViewPager)

chenruibing
 chenruibing
发布于 2015/05/30 11:46
字数 2078
阅读 22
收藏 0

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

完全由自己写的view,不依赖现成做好的工具,属于高级水平。

RadioGroup的用法


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <RadioGroup
        android:id="@+id/radioGroup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
    </RadioGroup>

    <com.itheima.myscrollview28.MyScrollView
        android:id="@+id/myscroll_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
一个无关紧要的view,用来试验横划和竖划的不同处理方式


GestureDetector手势识别工具,本质应该是对onTouchEvent进行封装。

onMeasure确定控件(view)的大小

onLayout对子view进行布局,确定子view的位置

onInterceptTouchEvent是否中断事件,也就是拦截事件自己进行处理。

onTouchEvent触摸事件

MyPageChangedListener自己写的接口,暴露给外部使用。类似于模仿监听器。

invalidate();  会导致  computeScroll()这个方法的执行

MyScroller   计算位移距离的工具类,实现有渐变的,缓慢的效果瞬间移动不好看


package com.itheima.myscrollview28;

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

public class MyScrollView extends ViewGroup{

	private Context ctx;

	/**
	 * 判断是否发生快速滑动
	 */
	protected boolean isFling;
	
	//xml布局调用的是这第二个构造方法
	public MyScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		this.ctx = context;
		initView();
	}

	private void initView() {
		
//		myScroller = new MyScroller(ctx);
		myScroller = new Scroller(ctx);
		//OnGestureListener的包要自己复制粘贴  无法import
		detector = new GestureDetector(ctx, new OnGestureListener() {
			
			@Override
			public boolean onSingleTapUp(MotionEvent e) {
				return false;
			}
			
			@Override
			public void onShowPress(MotionEvent e) {
			}
			
			@Override
			/**
			 * 响应手指在屏幕上的滑动事件
			 */
			public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
					float distanceY) {

				//移动屏幕 
//				System.out.println("distanceX::"+distanceX);
				
				/**
				 * 移动当前view内容 移动一段距离
				 * disX	 X方向移的距离		为正是,图片向左移动,为负时,图片向右移动 
				 * disY  Y方向移动的距离
				 * 内部调用的是scrollTo方法
				 */
				scrollBy((int) distanceX, 0);
				
				/**
				 * 将当前视图的基准点移动到某个点  坐标点
				 * x 水平方向X坐标
				 * Y 竖直方向Y坐标
				 *  scrollTo(x,  y);
				 */
				
				return false;
			}
			
			@Override
			public void onLongPress(MotionEvent e) {
			}
			
			@Override
			/**
			 * 发生快速滑动时的回调
			 */
			public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
					float velocityY) {
				isFling = true;
				if(velocityX>0 && currId>0){ // 快速向右滑动
					currId--;
				}else if(velocityX<0 && currId<getChildCount()-1){ // 快速向左滑动
					currId++;
				}
				//快速滑动  那么就不用判断是否滑动超过一半,只要滑动就过页
				moveToDest(currId);
				
				return false;
			}
			
			@Override
			public boolean onDown(MotionEvent e) {
				return false;
			}
		});
		
	}

	@Override
	/**
	 * 计算 控件大小,
	 * 做为viewGroup 还有一个责任,,:计算 子view的大小
	 * 不计算子view的大小的话也是可以排列出来的,只不过如果这个子view是布局文件的话,那就不行了
	 */
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		
		int size = MeasureSpec.getSize(widthMeasureSpec);
		int mode = MeasureSpec.getMode(widthMeasureSpec);
		
		
		for (int i = 0; i < getChildCount(); i++) {
			View v = getChildAt(i);
			v.measure(widthMeasureSpec, heightMeasureSpec);
			
			
//			v.getMeasuredWidth() // 得到测量的大小,也就是想要的大小
		}
		
	}
	
	@Override
	/**
	 * 对子view进行布局,确定子view的位置
	 * changed  若为true ,说明布局发生了变化
	 * l\t\r\b\  是指当前viewgroup 在其父view中的位置 
	 * 
	 * 说到底就是在这个方法指定各个view的排放方式
	 */
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		
		for (int i = 0; i < getChildCount(); i++) {
			View view = getChildAt(i); // 取得下标为I的子view
			
			
			/**
			 * 父view 会根据子view的需求,和自身的情况,来综合确定子view的位置,(确定他的大小)
			 */
			//指定子view的位置  ,  左,上,右,下四个点,是指在viewGround坐标系中的位置
			view.layout(0+i*getWidth(), 0, getWidth()+i*getWidth(), getHeight());	
			
			//onMeasure的getMeasuredWidth是测量的大小
//			view.getWidth();  得到view的真实的大小。 
			//没有执行onLayout的话,获取的值就是0,是在layout这里替他赋值的
		}
		
	}
	
	/**
	 * 判断是否要中断事件
	 * 默认是false  就是不中断,按照touch事件的流程来走
	 * 可以改为true 那么就中断,就不再把touch事件传给子孩子
	 * 举例:如果中断,那个布局文件就可以左右滑动,不能上下滑动,因为事件在这个ViewGroup上
	 * 		如果没中断,那么就可以上下滑动,不能左右滑动,因为事件在listView上
	 */
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// TODO Auto-generated method stub
		return super.onInterceptTouchEvent(ev);
	}
	
	
	/**
	 * 手势识别的工具类
	 */
	private GestureDetector detector;
	
	/**
	 * 当前的ID值
	 * 显示在屏幕上的子View的下标
	 */
	private int currId = 0;
	
	/**
	 * down 事件时的x坐标
	 */
	private int firstX = 0;
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		super.onTouchEvent(event);
		
		detector.onTouchEvent(event);
		
		//添加自己的事件解析
		
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			firstX = (int) event.getX();
			
			break;
		case MotionEvent.ACTION_MOVE:
			
			break;
		case MotionEvent.ACTION_UP:
			
			//不是快速滑动  那么就是滑动,这才是自己要的
			if(!isFling){//  在没有发生快速滑动的时候,才执行按位置判断currid
				int nextId = 0;
				if(event.getX()-firstX>getWidth()/2){ // 手指向右滑动,超过屏幕的1/2  当前的currid - 1
					nextId = currId-1;
				}else if(firstX - event.getX()>getWidth()/2){ // 手指向左滑动,超过屏幕的1/2  当前的currid + 1
					nextId = currId+1;
				}else{
					nextId = currId;
				}
				
				moveToDest(nextId);
//				scrollTo(0, 0);
			}
		
			isFling = false;
			
			break;
		}
		
		return true; 
	}

	/**
	 * 计算位移的工具类
	 */
//	private MyScroller myScroller;
	//系统的scroller  逻辑和MyScroller一样,方法名也是一样的
	private Scroller myScroller;
	
	
	/**
	 * 移动到指定的屏幕上
	 * @param nextId	屏幕 的下标
	 */
	public  void moveToDest(int nextId) {
		
		/*
		 * 对 nextId 进行判断 ,确保 是在合理的范围  
		 * 即  nextId >=0  && next <=getChildCount()-1
		 */
		
		//确保 currId>=0
		currId = (nextId>=0)?nextId:0;
		
		//确保 currId<=getChildCount()-1
		currId = (nextId<=getChildCount()-1)?nextId:(getChildCount()-1);
		
		//瞬间移动
//		scrollTo(currId*getWidth(), 0);
		
		//触发listener事件
		if(pageChangedListener!=null){
			pageChangedListener.moveToDest(currId);
		}
		
		//瞬间移动不好看  要有渐变的,缓慢的效果
		int distance = currId*getWidth() - getScrollX(); // 最终的位置 - 现在的位置  = 要移动的距离
		
//		myScroller.startScroll(getScrollX(),0,distance,0);
		//设置运行的时间  把时间设置为distance  系统默认是200毫秒  在MyScroller用的是500毫秒
		myScroller.startScroll(getScrollX(),0,distance,0,Math.abs(distance));
		/*
		 * 刷新当前view   onDraw()方法 的执行
		 * 这个方法很复杂,里面还有很多方法
		 */
		invalidate();
	}
	
	@Override
	/**
	 * invalidate();  会导致  computeScroll()这个方法的执行
	 */
	public void computeScroll() {
		if(myScroller.computeScrollOffset()){
			int newX = (int) myScroller.getCurrX();
			System.out.println("newX::"+newX);
			
			scrollTo(newX, 0);
			
			invalidate();
		};
	}

	public MyPageChangedListener getPageChangedListener() {
		return pageChangedListener;
	}

	public void setPageChangedListener(MyPageChangedListener pageChangedListener) {
		this.pageChangedListener = pageChangedListener;
	}

	private MyPageChangedListener pageChangedListener;
	
	/**
	 * 页面改时时的监听接口
	 * @author leo
	 *
	 */
	public interface MyPageChangedListener{
		void moveToDest(int currid);
	}
	
}
MainActivity



package com.itheima.myscrollview28;


import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;

import com.itheima.myscrollview28.MyScrollView.MyPageChangedListener;

public class MainActivity extends Activity {

	private MyScrollView msv;
	
	//图片资源ID 数组
	private int[] ids = new int[]{R.drawable.a1,R.drawable.a2,R.drawable.a3,R.drawable.a4,R.drawable.a5,R.drawable.a6};
	
	private RadioGroup radioGroup;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		msv =(MyScrollView) findViewById(R.id.myscroll_view);
		radioGroup = (RadioGroup) findViewById(R.id.radioGroup);
		
	
		//把图形添加进来
		for (int i = 0; i < ids.length; i++) {
			ImageView image = new ImageView(this);
			image.setBackgroundResource(ids[i]);
			msv.addView(image);
			
			
		}
		
		//让radioButton随着图片的改变而改变
		msv.setPageChangedListener(new MyPageChangedListener() {
			
			@Override
			public void moveToDest(int currid) {
				((RadioButton)radioGroup.getChildAt(currid)).setChecked(true);
			}
		});
		
		//点击选择就触发
		radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
			
			@Override
			public void onCheckedChanged(RadioGroup group, int checkedId) {
				msv.moveToDest(checkedId);
				
			}
		});
		
		//给自定义viewGroup添加测试的布局
		View temp = getLayoutInflater().inflate(R.layout.temp, null);
		//要在MyScrollView复写onMeasure方法,为每个view测量大小,要不然这个view的内容不会显示
		//不复写的话其他view也能显示  就这个view显示不了,因为这是一个布局文件,一定要计算大小才会排版
		msv.addView(temp, 2);
		
		
		for (int i = 0; i < msv.getChildCount(); i++) {
			//添加radioButton
			RadioButton rbtn = new RadioButton(this);
			//把下标做为id值  这样方便setOnCheckedChangeListener中moveToDest的使用
			rbtn.setId(i);
			
			radioGroup.addView(rbtn);
			if(i == 0){
				rbtn.setChecked(true);
			}
		}
		
	}
}

计算位移距离的工具类MyScroller

package com.itheima.myscrollview28;

import android.content.Context;
import android.os.SystemClock;

/**
 * 计算位移距离的工具类
 * @author leo
 *
 */
public class MyScroller {

	
	private int startX;
	private int startY;
	private int distanceX;
	private int distanceY;
	
	/**
	 * 开始执行动画的时间
	 */
	private long startTime;
	/**
	 * 判断是否正在执行动画
	 * true 是还在运行
	 * false  已经停止
	 */
	private boolean isFinish;

	public MyScroller(Context ctx){
		
	}

	/**
	 * 开移移动
	 * @param startX	开始时的X坐标
	 * @param startY	开始时的Y坐标
	 * @param disX		X方向 要移动的距离
	 * @param disY		Y方向 要移动的距离
	 */
	public void startScroll(int startX, int startY, int disX, int disY) {
		this.startX = startX;
		this.startY = startY;
		this.distanceX = disX;
		this.distanceY = disY;
		//从手机开机,也就是启动到现在的毫秒值
		this.startTime = SystemClock.uptimeMillis();
		
		this.isFinish = false;
	}

	/**
	 * 默认运行的时间
	 * 毫秒值
	 */
	private int duration = 500;
	/**
	 * 当前的X值
	 */
	private long currX;
	
	/**
	 * 当前的Y值
	 */
	private long currY;
	
	/**
	 * 计算一下当前的运行状况
	 * 返回值:
	 * true  还在运行
	 * false 运行结束
	 */
	public boolean computeScrollOffset() {

		if (isFinish) {
			return false;
		}

		// 获得所用的时间
		long passTime = SystemClock.uptimeMillis() - startTime;

		// 如果时间还在允许的范围内
		if (passTime < duration) {
			
			// 当前的位置  =  开始的位置  +  移动的距离(距离 = 速度*时间)
			currX = startX + distanceX * passTime / duration;
			currY = startY + distanceY * passTime / duration;

		} else {
			currX = startX + distanceX;
			currY = startY + distanceY;
			isFinish = true;
		}

		return true;
	}

	public long getCurrX() {
		return currX;
	}

	public void setCurrX(long currX) {
		this.currX = currX;
	}
	
	
}





© 著作权归作者所有

chenruibing
粉丝 12
博文 203
码字总数 109101
作品 0
潮州
私信 提问
android中ViewPager详解--视图滑动、界面卡等效果 (一)

这是谷歌官方给我们提供的一个兼容低版本安卓设备的软件包,里面包囊了只有在安卓3.0以上可以使用的api。而viewpager就是其中之一。利用它,我们可以做很多事情,从最简单的导航,到页面菜单...

一别经年
2014/01/21
4.8K
0
Android开源控件ViewPager Indicator的使用方法

Android Viewpager Indicator是Android开发中最常用的控件之一,几乎所有的新闻类APP中都有使用,下面介绍其基本使用方法。 1. ViewPager Indicator的Library 查看Viewpager Indicator的Lib...

linsea
2014/03/14
54.8K
0
TabLayout实现顶部导航(一)

前言 顶部导航栏,是我们在开发中比较常见的一种显示布局,它的实现可以有多种方式,那么今天我们就来讲讲 TabLayout 对它的实现。 此篇文章参考以下链接 TabLayout轻松实现仿今日头条顶部t...

奔跑的佩恩
01/07
0
0
ViewPager+RadioButton仿QQ效果

RadioButton是自己定制的, 设置android:button="@null" 然后再设置backgroud。 涉及到的问题: 1、如何让RadioGroup在底部,且RadioGroup和Viewpager互不遮挡: 先定义RadioGroup,设置ali...

亓斌哥哥
2014/09/21
10.3K
2
一起学Android之ViewPager

本文以一个简单的小例子,简述在Android开发中ViewPager的常见用法,仅供学习分享使用。 概述 ViewPager是一个支持使用者左右滑动的布局管理控件,可以通过一个实现的(适配器)PageAdapter...

Alan.hsiang
01/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

mars-config 动态配置管理

mars-config 码云地址:https://gitee.com/fashionbrot/mars-config 介绍 spring mvc 、springboot 动态配置系统。http 轮训方式 更新 动态配置 软件架构 软件架构说明 后端使用技术 :sprin...

fashionbrot
28分钟前
9
0
女朋友玩吃鸡手游被开挂老哥骗炮,我见义勇为将骗子绳之以法

大家好,我是乔哥。 晚上10点以后下班后我回到自如出租房里面,开始处理公众号粉丝发来的消息,一条一条处理,突然看到了这么几条消息,吸引了我的眼球: 然后我就和这位女粉丝小红(化名)聊...

gzc426
33分钟前
4
0
两款软件

fadetop保护眼睛软件 Snipaste截图软件

伟大源于勇敢的开始
今天
6
0
06.全局锁和表锁

根据加锁的范围,MySQL里面的锁大致可以分成全局锁、表级锁和行锁三类。 全局锁 全局锁就是对整个数据库实例加锁。MySQL提供了一个加全局读锁的方法,命令是flush tables with read lock(FTW...

scgaopan
今天
7
0
图解安装CentOS8

最近正式发布了CentOS8!迫不及待地准备下载了CentOS8镜像,准备体验下,工作繁忙无暇理会。 今天抽空安装体验下~ 可从CentOS官网下载:https://centos.org/download/ 为了快速可以选择从国...

技术训练营
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部