自定义ViewGroup

            本博文Demo下载地址:http://download.csdn.net/detail/stevenhu_223/8527997

     前言:很久之前写了一篇文章基于第三方开发Android锁屏,里面有用到自定义ViewGroup(StarLockView类),今天回头去看看当初写的这个类,简直惨不忍睹。用了一大堆变量,相关视图也在代码中创建,代码健壮性也有待提高。今天就把当初写的这个StarLockView结合自定义ViewGrou的知识重新处理一下。

 

  总的来说,StarLockView主要还是用来显示五个图标,外圈四个图标围着中心一个大图(常见的锁屏布局)。中心图标可以拖动,运动范围在一个圆圈内,而这个圆圈的半径就是外圈四个图标到中心图标圆心的距离。那么为了使这个半径可以自己设置,可以将这个半径radius作为StarLockView中的自定义属性。

  创建自定义ViewGroup的主要有以下四点:

  1.定义和声明需求的自定义属性。

  2.在引用自定义ViewGroup的布局文件中,创建命名空间,自定义属性赋值。

  3.覆盖onMeasure方法,进行相关的测量操作。

  4.覆盖onLayout方法,布局已测量计算后的子View。


 接下来,将结合以上四点创建StarLockView。

   1.创建attrs.xml文件,在该文件中定义和声明属性radius,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <declare-styleable name="StarLock">  
        <attr name="radius" format="dimension"/>
    </declare-styleable>
    
</resources>
    2.在主布局文件activity_main.xml中,创建命名空间,自定义属性赋值,代码如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:lock="http://schemas.android.com/apk/res/com.stevenhu.lock"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/suoping"
    tools:context=".MainActivity" >

    <com.stevenhu.lock.custom.StarLockView
        android:id="@+id/view_star_lock"
        android:layout_width="match_parent"
        android:layout_height="300dip"
        android:layout_alignParentBottom="true"
        lock:radius="100dip" 
        >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:scaleType="fitCenter"
            android:src="@drawable/camera"/>
        
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:scaleType="fitCenter"
            android:src="@drawable/home"/>
        
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:scaleType="fitCenter"
            android:src="@drawable/sms"/>
        
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:scaleType="fitCenter"
            android:src="@drawable/dial"/>
        
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:scaleType="fitCenter"
            android:src="@drawable/centure1"/>
        
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:scaleType="fitCenter"
            android:src="@drawable/centure2"/>
    </com.stevenhu.lock.custom.StarLockView>

</RelativeLayout>
   代码中lock为自己创建的命名空间,lock:radius="100dip" 为自定义属性赋值。这里半径设置为100dip。

 3.创建StarLockView,继承自ViewGroup。代码如下:

package com.stevenhu.lock.custom;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.ImageView;

import com.stevenhu.lock.R;
import com.stevenhu.lock.utils.Constant;

public class StarLockView extends ViewGroup {

	//半径(即外圈四个图标距离中心图标的距离)
	private int radius;
	
	private ImageView mCenterView, mAlphaView;
	//短信、拨号、相机、解锁图标对应的imageview
	private ImageView mSmsView, mDialView, mCameraView, mUnLockView;
	private AlphaAnimation alpha; //中心图标动画
	private boolean mTracking = false;
	private Rect mCenterViewRect; //中心图标可拖拽区域
	//短信、拨号、相机、解锁图标触发区域
	private Rect smsRect, dialRect, cameraRect, unlockRect;
	
	private int mCenterViewWidth, mCenterViewHeight;
	private float mx;
	private float my;
	
	//回调接口
	private OnLaunchListener mOnLaunchListener;
	
	public void setOnLaunchListener(OnLaunchListener listener) {
		this.mOnLaunchListener = listener;
	}

	public StarLockView(Context context) {
		super(context, null);
	}

	public StarLockView(Context context,AttributeSet attrs) {
		this(context, attrs, 0);
	}
	
	public StarLockView(Context context,AttributeSet attrs,int defStyle) {
		super(context, attrs, defStyle);
		
		TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, 
				R.styleable.StarLock, defStyle, 0);
		radius = typedArray.getDimensionPixelSize(R.styleable.StarLock_radius, 
				(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80, getResources().getDisplayMetrics()));
	}
	
	@Override
	protected void onAnimationStart() {
		super.onAnimationStart();
		mAlphaView.setVisibility(View.VISIBLE);

		if (alpha == null) {
			alpha = new AlphaAnimation(0.0f, 1.0f);
			alpha.setDuration(1000);
		}
		alpha.setRepeatCount(Animation.INFINITE);
		mAlphaView.startAnimation(alpha);
	}
	
	@Override
	protected void onAnimationEnd() {
		super.onAnimationEnd();
		if (alpha != null)
		{
			alpha = null;
		}
		mAlphaView.setAnimation(null);
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		//测量所有子View的宽高
		measureChildren(widthMeasureSpec, heightMeasureSpec);
	}
	

	@Override
	protected void onLayout(boolean changed,int l,int t,int r,int b) {
		
		int childCount = getChildCount();
		int childWidth = 0;
		int childHeight = 0;
		
		/** 
         * 遍历所有childView,根据其测量的宽和高布局
         */ 
		
		for (int i = 0 ; i < childCount; i ++) {
			View childView = getChildAt(i);
			childWidth = childView.getMeasuredWidth();
			childHeight = childView.getMeasuredHeight();
			
			int cl = 0, ct = 0, cr = 0, cb = 0;
			
			switch (i) {
			case 0:
				//相机位置
				cl = getWidth()/2 - childWidth/2;
				ct = getHeight()/2 - radius - childHeight/2;
				mCameraView = (ImageView)getChildAt(i);
				cameraRect = new Rect(cl, ct, cl + childWidth, ct + childHeight);
				break;

			case 1:
				//解锁位置
				cl = getWidth()/2 - childWidth/2;
				ct = getHeight()/2 + radius - childHeight/2;
				mUnLockView = (ImageView)getChildAt(i);
				unlockRect = new Rect(cl, ct, cl + childWidth, ct + childHeight);
				break;
				
			case 2:
				//短信位置
				cl = getWidth()/2 + radius - childWidth/2;
				ct = getHeight()/2 - childHeight/2;
				mSmsView = (ImageView)getChildAt(i);
				smsRect = new Rect(cl, ct, cl + childWidth, ct + childHeight);
				break;
				
			case 3:
				//拨号位置
				cl = getWidth()/2 - radius - childWidth/2;
				ct = getHeight()/2 - childHeight/2;
				mDialView = (ImageView)getChildAt(i);
				dialRect = new Rect(cl, ct, cl + childWidth, ct + childHeight);
				break;
				
			case 4:
				//中心图标
				cl = getWidth()/2 - childWidth/2;
				ct = getHeight()/2 - childHeight/2;
				mCenterView = (ImageView)getChildAt(i);
				mCenterViewWidth = childWidth;
				mCenterViewHeight = childHeight;
				break;
				
			case 5:
				//中心可变图
				cl = getWidth()/2 - childWidth/2;
				ct = getHeight()/2 - childHeight/2;
				mAlphaView = (ImageView)getChildAt(i);
				mCenterViewRect = new Rect(cl, ct, cl + childWidth, childHeight + ct);
				break;
			}
			
			 cr = cl + childWidth;  
	         cb = childHeight + ct;  
	         childView.layout(cl, ct, cr, cb);  
		}
		
		//启动中心图标动画
		onAnimationStart();
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		final int action = ev.getAction();
		final float x = ev.getX();
		final float y = ev.getY();

		switch (action) {
		case MotionEvent.ACTION_DOWN:
			//手指点在中心图标范围区域内
			if (mCenterViewRect.contains((int) x, (int) y)) 
			{
				mTracking = true;
				onAnimationEnd();
				mAlphaView.setVisibility(View.INVISIBLE);
				return true;
			} 
			
			break;

		default:
			break;
		}
		//此处返回false,onClick事件才能监听的到
		return false;
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		/*mTracking为true时,说明中心图标被点击移动
		 * 即只有在中心图标被点击移动的情况下,onTouchEvent
		 * 事件才会触发。
		 */
		if (mTracking)
		{
			final int action = event.getAction();
			final float nx = event.getX();
			final float ny = event.getY();

			switch (action) {
			case MotionEvent.ACTION_DOWN:
				break;
			case MotionEvent.ACTION_MOVE:
			     //setTargetViewVisible(nx, ny);
				 //中心图标移动
				 handleMoveView(nx, ny);
				break;
			case MotionEvent.ACTION_UP:
				 mTracking = false;
				 doTriggerEvent(mx, my);
				 resetMoveView();
				break;
			case MotionEvent.ACTION_CANCEL:
				 mTracking = false;
				 doTriggerEvent(mx, my);
				 resetMoveView();
				break;
			}
		}
		return mTracking || super.onTouchEvent(event);
	}
	
	//处理解锁、启动拨号、相机、短信应用
	private void doTriggerEvent(float a, float b) {
		if (smsRect.contains((int)a, (int) b))
		{
			launch(mSmsView, Constant.MSG_LAUNCH_SMS);
		}
		else if (dialRect.contains((int)a , (int)b))
		{
			launch(mDialView, Constant.MSG_LAUNCH_DIAL);
		}
		else if (cameraRect.contains((int)a, (int)b))
		{
			launch(mCameraView, Constant.MSG_LAUNCH_CAMERA);
		}
		else if (unlockRect.contains((int)a, (int)b))
		{
			launch(mUnLockView, Constant.MSG_LAUNCH_HOME);
		}
	}
	
	//回调处理
	private void launch(ImageView view, int mode) {
		onAnimationEnd();
		setTargetViewInvisible(view);
		virbate();
		mOnLaunchListener.onLaunch(mode);
	}

	private void setTargetViewInvisible(ImageView img)
	{
		img.setVisibility(View.INVISIBLE);	
	}
	
	//解锁时震动
	private void virbate()
	{
		Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
		vibrator.vibrate(200);
	}
		
	//处理中心圆圈运动
	private void handleMoveView(float x, float y)
	{
		
		int mHalfCenterViewWidth = mCenterViewWidth >> 1;
			
		//Radius为中心图标移动的限定的圆范围区域半径
		int Radius = mCenterViewWidth + mHalfCenterViewWidth;
		
		/*若用户手指移动的点与中心点的距离长度大于Radius,则中心图标坐标位置限定在移动区域范围圆弧上。
		 * 一般是用户拖动中心图标,手指移动到限定圆范围区域外。
		 */
		if (Math.sqrt(dist2(x - getWidth()/2, y - (mCenterView.getTop() + mCenterViewWidth / 2)
				)) > Radius)		
		{
			//原理为x1 / x = r1 / r
			x = (float) ((Radius / (Math.sqrt(dist2(x - getWidth()/2, y - (mCenterView.getTop() + mHalfCenterViewWidth)
			)))) * (x - getWidth()/2) + getWidth()/2);
			
			y = (float) ((Radius / (Math.sqrt(dist2(x - getWidth()/2, y - (mCenterView.getTop() + mHalfCenterViewWidth)
			)))) * (y - (mCenterView.getTop() + mHalfCenterViewWidth)) + mCenterView.getTop() + mHalfCenterViewWidth);
		}
		
		mx = x;
		my = y;
		/*图形的坐标是以左上角为基准的,
		 * 所以,为了使手指所在的坐标和图标的中心位置一致,
		 * 中心坐标要减去宽度和高度的一半。
		 */
		mCenterView.setX((int)x - mCenterView.getWidth()/2);
		mCenterView.setY((int)y - mCenterView.getHeight()/2);
	    invalidate();
	}
	
	//重置中心图标,回到原位置
	private void resetMoveView()
	{
		mCenterView.setX(getWidth() / 2 - mCenterViewWidth /2);
		mCenterView.setY((mCenterView.getTop() + mCenterViewHeight / 2) - mCenterViewHeight / 2);
		onAnimationStart();
		invalidate();
	}
		
	//平方和计算
	private float dist2(float dx, float dy)
	{
		return dx * dx + dy * dy;
	}
	
	//定义回调接口
	public interface OnLaunchListener {
		void onLaunch(int mode);
	}
	
}
  在 StarLockView的onMeasure中调用measureChildren方法对其包含的子View进行测量。而在onLayout中对其包含的子View进行遍历,根据这些子View测量所得的宽高,逐个调用其layout方法对其进行布局。

  4.主Activity代码如下:

package com.stevenhu.lock;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.provider.MediaStore;
import com.stevenhu.lock.custom.StarLockView;
import com.stevenhu.lock.custom.StarLockView.OnLaunchListener;
import com.stevenhu.lock.utils.Constant;

public class MainActivity extends Activity implements OnLaunchListener{
	
	private StarLockView mStarLockView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initialize();
	}

	private void initialize() {
		mStarLockView = (StarLockView)findViewById(R.id.view_star_lock);
		mStarLockView.setOnLaunchListener(this);
	}

	@Override
	public void onLaunch(int mode) {
		switch (mode) {
		case Constant.MSG_LAUNCH_CAMERA:
			launchCamera();
			finish();
			break;

		case Constant.MSG_LAUNCH_DIAL:
			launchDial();
			finish();
			break;
			
		case Constant.MSG_LAUNCH_HOME:
			finish();
			break;
			
		case Constant.MSG_LAUNCH_SMS:
			launchSms();
			finish();
			break;
		}
	}
	
	 //启动短信应用
    private void launchSms() {

		Intent intent = new Intent();
		ComponentName comp = new ComponentName("com.android.mms",
				"com.android.mms.ui.ConversationList");
		intent.setComponent(comp);
		intent.setAction("android.intent.action.VIEW");
		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
				| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
		startActivity(intent);
	}
    
    //启动拨号应用
    private void launchDial() {
		Intent intent = new Intent(Intent.ACTION_DIAL);
		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
				| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
		startActivity(intent);
	}
    
    //启动相机应用
    private void launchCamera() {
		
		Intent intent = new Intent(); 
		intent.setAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); 
		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		startActivity(intent); 
	}
    
    //使返回键无效
    @Override
    public void onBackPressed() {
    	
    }
    
}


  最终效果图如下:





  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值