本博文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() {
}
}
最终效果图如下: