日期: 2021 年 9 月 4 日

android在Service中弹出Dialog对话框,即全局性对话框

android在Service中弹出Dialog对话框,即全局性对话框

先说具体做法,原因在其后给出:

写好Alter功能块后,在alter.show()语句前加入:

  1. alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);

注:alter为AlertDialog类型对象

然后在AndroidManifest.xml中加入权限:

 

 

  1. <uses-permission android:name=“android.permission.SYSTEM_ALERT_WINDOW”></uses-permission>  

下面进行简单的解释:如果只在Service中写入常在Activity中使用的创建Alter的代码,运行时是会发生错误的,因为Alter的显示需要依附于一个确定的Activity类。而以上做法就是声明我们要弹出的这个提示框是一个系统的提示框,即全局性质的提示框,所以只要手机处于开机状态,无论它现在处于何种界面之下,只要调用alter.show(),就会弹出提示框来。

Dialog详解(包括进度条、PopupWindow、自定义view、自定义样式的对话框)

Dialog详解(包括进度条、PopupWindow、自定义view、自定义样式的对话框)

%title插图%num

Android中提供了多种对话框,在实际应用中我们可能会需要修改这些已有的对话框。本实例就是从实际出发,展现了andorid中大部分对话框,代码中用了一个对话框管理类来做封装,其中还定义了对话框的动画、自定义样式等等。

 

主布局文件(全是button)

复制代码
复制代码
<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:orientation="vertical"
    android:padding="16dp" >

    <ScrollView
        android:id="@+id/scrollView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >

            <Button
                android:id="@+id/simple_button_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="buttonListener"
                android:text="*简单的对话框" />

            <Button
                android:id="@+id/list_button_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="buttonListener"
                android:text="列表对话框" />

            <Button
                android:id="@+id/singleChoice_button_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="buttonListener"
                android:text="单选对话框" />

            <Button
                android:id="@+id/multiChoice_button_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="buttonListener"
                android:text="多选对话框" />

            <Button
                android:id="@+id/adapter_button_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="buttonListener"
                android:text="用适配器创建的对话框" />

            <Button
                android:id="@+id/view_button_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="buttonListener"
                android:text="自定义视图的对话框" />

            <Button
                android:id="@+id/progress_button_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="buttonListener"
                android:text="有进度条的对话框" />

            <Button
                android:id="@+id/activity_button_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="buttonListener"
                android:text="用Activity作的对话框" />

            <Button
                android:id="@+id/popup_button_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="buttonListener"
                android:text="用PopupWindow创建的对话框" />

            <Button
                android:id="@+id/date_button_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="buttonListener"
                android:text="选择日期的对话框" />

            <Button
                android:id="@+id/time_button_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="buttonListener"
                android:text="选择时间的对话框" />
        </LinearLayout>
    </ScrollView>

</LinearLayout>
复制代码
复制代码

 

自定义对话框视图

%title插图%num

复制代码
复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#f5f5f5"
        android:orientation="vertical" >

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="60dp" >

            <ImageView
                android:id="@+id/dialog_imageView_id"
                android:layout_width="match_parent"
                android:layout_height="3dp"
                android:layout_alignParentBottom="true"
                android:layout_alignParentLeft="true"
                android:background="#ffd060" />

            <TextView
                android:id="@+id/dialog_textView_id"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:layout_centerVertical="true"
                android:text="自定义布局"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:textColor="#50c0e9" />
        </RelativeLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            android:layout_marginTop="10dp"
            android:background="@drawable/my_input_box"
            android:paddingLeft="20dp"
            android:paddingRight="20dp" >

            <EditText
                android:id="@+id/dialog_username_EditText_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:background="@null"
                android:ems="10"
                android:hint="学号/账号"
                android:inputType="number" >

                <requestFocus />
            </EditText>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            android:background="@drawable/my_input_box"
            android:paddingLeft="20dp"
            android:paddingRight="20dp" >

            <EditText
                android:id="@+id/dialog_password_EditText_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:background="@null"
                android:ems="10"
                android:hint="密码"
                android:password="true" />
        </LinearLayout>

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_marginTop="10dp"
            android:background="#bababa" />

        <Button
            android:id="@+id/dialog_logout_button_id"
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:background="@drawable/layout_selector"
            android:gravity="center_horizontal"
            android:text="确定"
            android:textColor="#535252"
            android:textSize="20sp" />
    </LinearLayout>

</RelativeLayout>
复制代码
复制代码

 

MainActivity.java

复制代码
复制代码
package com.kale.dialog;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;


public class MainActivity extends Activity {

    DialogManager dm;
    String msg = "内容";
    String[] str = new String[] { "android", "java", "ios" };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dm = new DialogManager(this);

        
    }

    public void buttonListener(View v) {
        dm = new DialogManager(this);
        switch (v.getId()) {
        case R.id.simple_button_id:
            dm.simpleDialog("*简单的对话框", msg);
            break;
        case R.id.list_button_id:
            dm.listDialog("列表对话框", str);
            break;
        case R.id.singleChoice_button_id:
            dm.singleChoiceDialog("单选对话框", str);
            break;
        case R.id.multiChoice_button_id:
            dm.MultiChoiceDialog("多选对话框", str);
            break;
        case R.id.adapter_button_id:
            dm.adapterDialog("用适配器建立的对话框", str);
            break;
        case R.id.view_button_id:
            dm.viewDialog("采用自定义视图的对话框");
            break;
        case R.id.progress_button_id:
            dm.progressDialog("含进度条的对话框",msg);
            break;
        case R.id.activity_button_id:
            startActivity(new Intent(MainActivity.this,DialogActivity.class));
            break;
        case R.id.popup_button_id:
            dm.popupWindowDialog("PopupWindows对话框", v);
            break;
        case R.id.date_button_id:
            dm.dateDialog();
            break;
        case R.id.time_button_id:
            dm.timeDialog();
            break;            
        default:
            break;
        }
    }

}
复制代码
复制代码

 

 

 

现在我们分步讲解下各种对话框:

首先是一个公用的初始化设置和监听器设置

复制代码
复制代码
private Context mContext;
    private AlertDialog.Builder builder;

    public DialogManager(Context context) {
        mContext = context;
        builder = new AlertDialog.Builder(mContext);
    }

    /**
     * 设置对话框的标题+图标+按钮
     * 
     * @param title
     */
    private void setButton(String title) {
        builder.setTitle(title).setIcon(R.drawable.ic_launcher)
                .setPositiveButton("好", new positiveListener())
                .setNeutralButton("中", new NeutralListener())
                .setNegativeButton("差", new NegativeListener());
        // .setCancelable(false);//设置点击空白处,不能消除该对话框
    }

    /**
     * @author:Jack Tony
     * @tips : 监听器
     * @date :2014-7-25
     */
    private class positiveListener implements DialogInterface.OnClickListener {
        @Override
        public void onClick(DialogInterface dialog, int which) {

            // dialog.dismiss();//设置对话框强制退出
            showToast("好");

        }
    }

    private class NeutralListener implements DialogInterface.OnClickListener {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            showToast("中");
        }
    }

    private class NegativeListener implements DialogInterface.OnClickListener {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            showToast("差");
        }
    }

    private void showToast(String msg) {
        Toast.makeText(mContext, msg, 0).show();
    }
复制代码
复制代码

 

简易对话框

%title插图%num

复制代码
复制代码
    /**
     * 简易对话框
     * 
     * @param title
     * @param msg
     */
    public void simpleDialog(String title, String msg) {
        setButton(title);
        builder.setMessage(msg).create().show();
    }
复制代码
复制代码

 

列表对话框

%title插图%num

复制代码
复制代码
/**
     * 列表对话框
     * 
     * @param title
     * @param str
     */
    public void listDialog(String title, final String[] str) {
        setButton(title);
        // 设置了列表就不能设置内容了,否则就会出问题
        builder.setItems(str, new OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                showToast("选中了:" + str[which]);
            }
        }).create().show();
    }
复制代码
复制代码

 

单选对话框

%title插图%num

复制代码
复制代码
/**
     * 单选对话框
     * 
     * @param title
     * @param str
     */
    public void singleChoiceDialog(String title, final String[] str) {
        setButton(title);
        builder
        // 设置选中了第二项
        .setSingleChoiceItems(str, 1, new OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                showToast("选中了:" + str[which]);
            }
        }).create().show();
    }
复制代码
复制代码

 

多选对话框

%title插图%num

复制代码
复制代码
    /**
     * 多选对话框
     * 
     * @param title
     * @param str
     */
    public void MultiChoiceDialog(String title, final String[] str) {
        setButton(title);
        builder
        // 默认选中几项
        .setMultiChoiceItems(str, new boolean[] { true, false, true },
                new OnMultiChoiceClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which,
                            boolean isChecked) {
                        showToast("你选择的id为" + which + " , " + "选中了:"
                                + str[which]);
                    }
                }).create().show();
    }
复制代码
复制代码

 

适配器对话框(可以用各种适配器,比如SimpleAdapter)

%title插图%num

复制代码
复制代码
    /**
     * 适配器对话框
     * 
     * @param title
     * @param str
     */
    public void adapterDialog(String title, final String[] str) {
        setButton(title);
        builder.setAdapter(
                new ArrayAdapter<String>(mContext,
                        android.R.layout.simple_list_item_multiple_choice, str),
                new OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        showToast("选中了:" + str[which]);
                    }
                }).create().show();
    }
复制代码
复制代码

 

自定义视图的对话框

%title插图%num

复制代码
复制代码
/**
     * 自定义视图对话框
     * 
     * @param title
     */
    public void viewDialog(String title) {
        // LayoutInflater是用来找layout文件夹下的xml布局文件,并且实例化
        LayoutInflater factory = LayoutInflater.from(mContext);
        // 把activity_login中的控件定义在View中
        View view = factory.inflate(R.layout.dialog_layout, null);
        // 将LoginActivity中的控件显示在对话框中

        // 获取用户输入的“用户名”,“密码”
        // 注意:view.findViewById很重要,因为上面factory.inflate(R.layout.activity_login,
        // null)将页面布局赋值给了view了
        TextView titleTv = (TextView) view
                .findViewById(R.id.dialog_textView_id);
        titleTv.setText(title);
        Button btn = (Button) view.findViewById(R.id.dialog_logout_button_id);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                showToast("按下自定义视图的按钮了~");
            }
        });

        builder
        // 设定显示的View
        .setView(view);
        // 设置dialog是否为模态,false表示模态,true表示非模态
        // ab.setCancelable(false);
        // 对话框的创建、显示,这里显示的位置是在屏幕的*下面,但是很不推荐这个种做法,因为距底部有一段空隙
        AlertDialog dialog = builder.create();
        Window window = dialog.getWindow();
        window.setGravity(Gravity.BOTTOM); // 此处可以设置dialog显示的位置
        window.setWindowAnimations(R.style.myAnimationstyle); // 添加动画
        dialog.show();
    }
复制代码
复制代码

 

进度条对话框(这里可以定义显示精准进度的、模糊进度的、圆形模糊进度的进度条)

%title插图%num

复制代码
复制代码
    /**
     * 进度条对话框
     * 
     * @param title
     * @param msg
     */
    public void progressDialog(String title, String msg) {
        final ProgressDialog dialog = new ProgressDialog(mContext);
        dialog.setTitle(title);
        dialog.setMessage(msg);
        dialog.setCancelable(false);// 设置点击空白处也不能关闭该对话框

        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        // dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);//设置采用圆形进度条

        dialog.setMax(100);
        // dialog.setIndeterminate(true);//设置不显示明确的进度
        dialog.setIndeterminate(false);// 设置显示明确的进度

        dialog.setButton(ProgressDialog.BUTTON_POSITIVE, "确定",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // 这里添加点击后的逻辑
                    }
                });
        dialog.setButton(ProgressDialog.BUTTON_NEUTRAL, "中立",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // 这里添加点击后的逻辑
                    }
                });
        dialog.setButton(ProgressDialog.BUTTON_NEGATIVE, "取消",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // 这里添加点击后的逻辑
                    }
                });
        dialog.show();

        //启动线程,模拟一个耗时的操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                int Progress = 0;
                while (Progress < 100) {
                    try {
                        Thread.sleep(100);
                        Progress++;
                        // dialog.setProgress(Progress);
                        dialog.incrementProgressBy(1);// 进度条一次加10
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                dialog.dismiss();// 完成后消失
            }
        }).start();
    }
复制代码
复制代码

 

用PopupWindow做的对话框,PopupWindow特别适合显示从特定位置弹出的的菜单。我这里设置对话框是从底部弹出,用了一些动画效果。

%title插图%num

复制代码
复制代码
/**
     * PopupWindow做的对话框 感谢: http://www.apkbus.com/android-56965-1-1.html
     * http://blog.csdn.net/zhufuing/article/details/17783333
     * http://www.open-open.com/lib/view/open1379383271818.html
     * 
     * @param title
     * @param v
     */
    public void popupWindowDialog(String title, View v) {
        // 装载布局文件
        View view = LayoutInflater.from(mContext).inflate(
                R.layout.dialog_layout, null);
        // 创建PopupWindow对象,添加视图,设置宽高,*后一个参数为设置点击屏幕空白处(按返回键)对话框消失。
        // 也可以用.setFocusable(true);.
        final PopupWindow pWindow = new PopupWindow(view,
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, true);
        pWindow.setBackgroundDrawable(new BitmapDrawable());// 为了让对话框点击空白处消失,必须有这条语句
        pWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);// 出现输入法时,重新布局
        pWindow.setAnimationStyle(R.style.myAnimationstyle);// 设置动画

        TextView titleTv = (TextView) view
                .findViewById(R.id.dialog_textView_id);
        titleTv.setText(title);
        Button btn = (Button) view.findViewById(R.id.dialog_logout_button_id);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                showToast("按下PopupWindow中的按钮了~");
                pWindow.dismiss();
            }
        });
        // 用下拉方式显示
        // pWindow.showAsDropDown(v);
        pWindow.showAtLocation(v, Gravity.BOTTOM, 0, 0);
    }
复制代码
复制代码

动画效果和style文件

动画文件的目录 res/anim

dialog_enter.xml

复制代码
复制代码
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >

    <translate
        android:duration="200"
        android:fromYDelta="100%p"
        android:toYDelta="0" />

</set>
复制代码
复制代码

dialog_exit.xml

复制代码
复制代码
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >

    <translate
        android:duration="200"
        android:fromYDelta="0"
        android:toYDelta="50%p" />

</set>
复制代码
复制代码

样式文件的目录:values/styles.xml

复制代码
复制代码
<resources>
    
     <style name="myAnimationstyle" parent="android:Animation">  
        <item name="@android:windowEnterAnimation">@anim/dialog_enter</item>  
        <item name="@android:windowExitAnimation">@anim/dialog_exit</item>  
    </style>  
</resources>
复制代码
复制代码

 

日期对话框

%title插图%num

复制代码
复制代码
/**
     * 日期对话框
     */
    public void dateDialog() {
        Calendar c = Calendar.getInstance();
        DatePickerDialog dialog = new DatePickerDialog(mContext,
                new DatePickerDialog.OnDateSetListener() {

                    @Override
                    public void onDateSet(DatePicker dp, int year, int month,
                            int dayOfMonth) {
                        showToast(year + "-" + (month + 1) + "-" + dayOfMonth);
                    }
                }, c.get(Calendar.YEAR), c.get(Calendar.MONTH),
                c.get(Calendar.DAY_OF_MONTH));
        dialog.show();
    }
复制代码
复制代码

 

时间对话框

%title插图%num

复制代码
复制代码
    /**
     * 时间对话框
     */
    public void timeDialog() {
        Calendar c = Calendar.getInstance();
        new TimePickerDialog(mContext,
                new TimePickerDialog.OnTimeSetListener() {

                    @Override
                    public void onTimeSet(TimePicker arg0, int hourOfDay,
                            int minute) {
                        showToast(hourOfDay + ":" + minute);
                    }
                }, c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true)
                .show();
    }
复制代码
复制代码

 

用Activit做的对话框

%title插图%num

DialogActivity.java

复制代码
复制代码
package com.kale.dialog;

import android.app.Activity;
import android.os.Bundle;

public class DialogActivity extends Activity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.dialog_layout);
    }
}
复制代码
复制代码

 

首先,定义风格

复制代码
复制代码
    <!-- android Dialog去掉标题栏 和边框 -->
     <style name="myDialogTheme" parent="android:Theme.Dialog">
        <item name="android:windowFrame">@null</item><!--边框-->
        <item name="android:windowIsFloating">true</item><!--是否浮现在activity之上-->
        <item name="android:windowIsTranslucent">true</item> <!-- 是否透明 -->
        <item name="android:windowNoTitle">true</item><!--除去title-->
        <item name="android:windowContentOverlay">@null</item> <!-- 对话框是否有遮盖 -->
        <item name="android:backgroundDimEnabled">false</item><!-- 不允许对话框的背景变暗 -->
        <item name="android:windowBackground">@null</item><!--除去背景色-->
    </style>
复制代码
复制代码

然后,在AndroidManifest.xml中给activity设置风格

复制代码
复制代码
        <activity 
            android:name="com.kale.dialog.DialogActivity"
            android:theme="@style/myDialogTheme">
        </activity>
复制代码
复制代码

*后,启动这个activity即可

startActivity(new Intent(MainActivity.this,DialogActivity.class));

 

下面是DialogManager.java工具类的全部代码

复制代码
复制代码
package com.kale.dialog;

import java.util.Calendar;

import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.ProgressDialog;
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnMultiChoiceClickListener;
import android.graphics.drawable.BitmapDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.TimePicker;
import android.widget.Toast;

public class DialogManager {

    private Context mContext;
    private AlertDialog.Builder builder;

    public DialogManager(Context context) {
        mContext = context;
        builder = new AlertDialog.Builder(mContext);
    }

    /**
     * 简易对话框
     * 
     * @param title
     * @param msg
     */
    public void simpleDialog(String title, String msg) {
        setButton(title);
        builder.setMessage(msg).create().show();
    }

    /**
     * 列表对话框
     * 
     * @param title
     * @param str
     */
    public void listDialog(String title, final String[] str) {
        setButton(title);
        // 设置了列表就不能设置内容了,否则就会出问题
        builder.setItems(str, new OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                showToast("选中了:" + str[which]);
            }
        }).create().show();
    }

    /**
     * 单选对话框
     * 
     * @param title
     * @param str
     */
    public void singleChoiceDialog(String title, final String[] str) {
        setButton(title);
        builder
        // 设置选中了第二项
        .setSingleChoiceItems(str, 1, new OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                showToast("选中了:" + str[which]);
            }
        }).create().show();
    }

    /**
     * 多选对话框
     * 
     * @param title
     * @param str
     */
    public void MultiChoiceDialog(String title, final String[] str) {
        setButton(title);
        builder
        // 默认选中几项
        .setMultiChoiceItems(str, new boolean[] { true, false, true },
                new OnMultiChoiceClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which,
                            boolean isChecked) {
                        showToast("你选择的id为" + which + " , " + "选中了:"
                                + str[which]);
                    }
                }).create().show();
    }

    /**
     * 适配器对话框
     * 
     * @param title
     * @param str
     */
    public void adapterDialog(String title, final String[] str) {
        setButton(title);
        builder.setAdapter(
                new ArrayAdapter<String>(mContext,
                        android.R.layout.simple_list_item_multiple_choice, str),
                new OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        showToast("选中了:" + str[which]);
                    }
                }).create().show();
    }

    /**
     * 自定义视图对话框
     * 
     * @param title
     */
    public void viewDialog(String title) {
        // LayoutInflater是用来找layout文件夹下的xml布局文件,并且实例化
        LayoutInflater factory = LayoutInflater.from(mContext);
        // 把activity_login中的控件定义在View中
        View view = factory.inflate(R.layout.dialog_layout, null);
        // 将LoginActivity中的控件显示在对话框中

        // 获取用户输入的“用户名”,“密码”
        // 注意:view.findViewById很重要,因为上面factory.inflate(R.layout.activity_login,
        // null)将页面布局赋值给了view了
        TextView titleTv = (TextView) view
                .findViewById(R.id.dialog_textView_id);
        titleTv.setText(title);
        Button btn = (Button) view.findViewById(R.id.dialog_logout_button_id);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                showToast("按下自定义视图的按钮了~");
            }
        });

        builder
        // 设定显示的View
        .setView(view);
        // 设置dialog是否为模态,false表示模态,true表示非模态
        // ab.setCancelable(false);
        // 对话框的创建、显示,这里显示的位置是在屏幕的*下面,但是很不推荐这个种做法,因为距底部有一段空隙
        AlertDialog dialog = builder.create();
        Window window = dialog.getWindow();
        window.setGravity(Gravity.BOTTOM); // 此处可以设置dialog显示的位置
        window.setWindowAnimations(R.style.myAnimationstyle); // 添加动画
        dialog.show();
    }

    /**
     * 进度条对话框
     * 
     * @param title
     * @param msg
     */
    public void progressDialog(String title, String msg) {
        final ProgressDialog dialog = new ProgressDialog(mContext);
        dialog.setTitle(title);
        dialog.setMessage(msg);
        dialog.setCancelable(false);// 设置点击空白处也不能关闭该对话框

        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        // dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);//设置采用圆形进度条

        dialog.setMax(100);
        // dialog.setIndeterminate(true);//设置不显示明确的进度
        dialog.setIndeterminate(false);// 设置显示明确的进度

        dialog.setButton(ProgressDialog.BUTTON_POSITIVE, "确定",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // 这里添加点击后的逻辑
                    }
                });
        dialog.setButton(ProgressDialog.BUTTON_NEUTRAL, "中立",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // 这里添加点击后的逻辑
                    }
                });
        dialog.setButton(ProgressDialog.BUTTON_NEGATIVE, "取消",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // 这里添加点击后的逻辑
                    }
                });
        dialog.show();

        //启动线程,模拟一个耗时的操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                int Progress = 0;
                while (Progress < 100) {
                    try {
                        Thread.sleep(100);
                        Progress++;
                        // dialog.setProgress(Progress);
                        dialog.incrementProgressBy(1);// 进度条一次加10
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                dialog.dismiss();// 完成后消失
            }
        }).start();
    }

    /**
     * PopupWindow做的对话框 感谢: http://www.apkbus.com/android-56965-1-1.html
     * http://blog.csdn.net/zhufuing/article/details/17783333
     * http://www.open-open.com/lib/view/open1379383271818.html
     * 
     * @param title
     * @param v
     */
    public void popupWindowDialog(String title, View v) {
        // 装载布局文件
        View view = LayoutInflater.from(mContext).inflate(
                R.layout.dialog_layout, null);
        // 创建PopupWindow对象,添加视图,设置宽高,*后一个参数为设置点击屏幕空白处(按返回键)对话框消失。
        // 也可以用.setFocusable(true);.
        final PopupWindow pWindow = new PopupWindow(view,
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, true);
        pWindow.setBackgroundDrawable(new BitmapDrawable());// 为了让对话框点击空白处消失,必须有这条语句
        pWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);// 出现输入法时,重新布局
        pWindow.setAnimationStyle(R.style.myAnimationstyle);// 设置动画

        TextView titleTv = (TextView) view
                .findViewById(R.id.dialog_textView_id);
        titleTv.setText(title);
        Button btn = (Button) view.findViewById(R.id.dialog_logout_button_id);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                showToast("按下PopupWindow中的按钮了~");
                pWindow.dismiss();
            }
        });
        // 用下拉方式显示
        // pWindow.showAsDropDown(v);
        pWindow.showAtLocation(v, Gravity.BOTTOM, 0, 0);
    }

    /**
     * 日期对话框
     */
    public void dateDialog() {
        Calendar c = Calendar.getInstance();
        DatePickerDialog dialog = new DatePickerDialog(mContext,
                new DatePickerDialog.OnDateSetListener() {

                    @Override
                    public void onDateSet(DatePicker dp, int year, int month,
                            int dayOfMonth) {
                        showToast(year + "-" + (month + 1) + "-" + dayOfMonth);
                    }
                }, c.get(Calendar.YEAR), c.get(Calendar.MONTH),
                c.get(Calendar.DAY_OF_MONTH));
        dialog.show();
    }

    /**
     * 时间对话框
     */
    public void timeDialog() {
        Calendar c = Calendar.getInstance();
        new TimePickerDialog(mContext,
                new TimePickerDialog.OnTimeSetListener() {

                    @Override
                    public void onTimeSet(TimePicker arg0, int hourOfDay,
                            int minute) {
                        showToast(hourOfDay + ":" + minute);
                    }
                }, c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true)
                .show();
    }

    /**
     * 设置对话框的标题+图标+按钮
     * 
     * @param title
     */
    private void setButton(String title) {
        builder.setTitle(title).setIcon(R.drawable.ic_launcher)
                .setPositiveButton("好", new positiveListener())
                .setNeutralButton("中", new NeutralListener())
                .setNegativeButton("差", new NegativeListener());
        // .setCancelable(false);//设置点击空白处,不能消除该对话框
    }

    /**
     * @author:Jack Tony
     * @tips : 监听器
     * @date :2014-7-25
     */
    private class positiveListener implements DialogInterface.OnClickListener {
        @Override
        public void onClick(DialogInterface dialog, int which) {

            // dialog.dismiss();//设置对话框强制退出
            showToast("好");

        }
    }

    private class NeutralListener implements DialogInterface.OnClickListener {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            showToast("中");
        }
    }

    private class NegativeListener implements DialogInterface.OnClickListener {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            showToast("差");
        }
    }

    private void showToast(String msg) {
        Toast.makeText(mContext, msg, 0).show();
    }
}
复制代码
复制代码

AlertDialog禁止返回键

AlertDialog禁止返回键

android 如何让dialog不消失,即使是用户按了返回键dialog也不消失
解决的问题:软件提示升级的dialog时候,用户有可能按了返回键,但是现在的需求是用户只能按“确定升级”或者“暂时不升级”这两个按钮才能拿dialog消失,按返回键不能使dialog消失
方案:截取activity的 onkeydown事件,而应该截取dialog的key响应事件,当dialog在前台显示的时候,keylistener首先会派发到dialog里面,在那里面监听就行了。
先申明一个keylistener。
OnKeyListener keylistener = new DialogInterface.OnKeyListener(){
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode==KeyEvent.KEYCODE_BACK&&event.getRepeatCount()==0)
{
return true;
}
else
{
return false;
}
}
} ;
在把这个listener注册到dialog里面去 当初始化dialog的时候
builder.setTitle(getText(R.string.XXXX))
.setMessage(getText(R.XXXXXX))
.setOnKeyListener(key).setCancelable(false)
.setPositiveButton(android.R.string.ok, someOKButtonListener)
.setNegativeButton(android.R.string.cancel, null);

PS: setCancelable(false),作用是当dialog弹出来的时候,如果触点在dialog外围,按照默认的方式 dialog将消失。如果这个设为false的话 这种情况dialog就不会消失了。  加了这一句就OK了。dialog.setCancelable(false);

EditText限制输入字符类型的几种方式

EditText限制输入字符类型的几种方式

   *近的项目上需要限制EditText输入字符的类型,就把可以实现这个功能的方法整理了一下:

1、*种方式是通过EditText的inputType来实现,可以通过xml或者Java文件来设置。假如我要设置为显示密码的形式,可以像下面这样设置:

在xml中,   Android:inputType=”textPassword”

在java文件中,可以用 myEditText.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);

当然,还有更多的其他属性用来进行输入设置。

 

2、第二种是通过android:digits 属性来设置,这种方式可以指出要显示的字符,比如我要限制只显示数字,可以这样:

android:digits=”0123456789″

如果要显示的内容比较多,就比较麻烦了,将要显示的内容依次写在里面。

3、通过正则表达式来判断。下面的例子只允许显示字母、数字和汉字。

public static String stringFilter(String str)throws PatternSyntaxException{
// 只允许字母、数字和汉字
String   regEx  =  “[^a-zA-Z0-9\u4E00-\u9FA5]”;
Pattern   p   =   Pattern.compile(regEx);
Matcher   m   =   p.matcher(str);
return   m.replaceAll(“”).trim();
}

然后需要在TextWatcher的onTextChanged()中调用这个函数,

@Override
public void onTextChanged(CharSequence ss, int start, int before, int count) {
String editable = editText.getText().toString();
String str = stringFilter(editable.toString());
if(!editable.equals(str)){
editText.setText(str);
//设置新的光标所在位置
editText.setSelection(str.length());
}
}

 

4、通过InputFilter来实现。

 

实现InputFilter过滤器,需要覆盖一个叫filter的方法。

public abstract CharSequence filter (

CharSequence source,  //输入的文字

int start,  //开始位置

int end,  //结束位置

Spanned dest, //当前显示的内容

int dstart,  //当前开始位置

int dend //当前结束位置

);

下面的实现使得EditText只接收字符(数字、字母和汉字)和“-”“_”,Character.isLetterOrDigit会把中文也当做Letter。

editText.setFilters(new InputFilter[] {

new InputFilter() {
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
            for (int i = start; i < end; i++) {
                    if ( !Character.isLetterOrDigit(source.charAt(i)) && !Character.toString(source.charAt(i)) .equals(“_”) && !Character.toString(source.charAt(i)) .equals(“-“)) {
                            return “”;
                    }
            }
            return null;

}  });

 

另外使用InputFilter还能限制输入的字符个数,如

EditText tv =newEditText(this);
int maxLength =10;
InputFilter[] fArray =new InputFilter[1];
fArray[0]=new  InputFilter.LengthFilter(maxLength);
tv.setFilters(fArray);

上面的代码可以限制输入的字符数*大为10。

Android 手势识别类 ( 三 ) GestureDetector 源码浅析

Android 手势识别类 ( 三 ) GestureDetector 源码浅析

  前言:上 篇介绍了提供手势绘制的视图平台GestureOverlayView,但是在视图平台上绘制出的手势,是需要存储以及在必要的利用时加载取出手势。所 以,用户绘制出的一个完整的手势是需要一定的代码机制来完成存储以及必要时加载取出的;那么,在源码中Gesture这个类就是用来描述完整的手势的。一 个Gesture就是用户手指在触摸屏上绘制形成的不规则几何图形(A gesture is a hand-drawn shape on a touch screen);

   一. Gesture的组成

我们知道,当我们在GestureOverlayView上绘制手势时,形成的不规则几何图形是由多数个点形成的,这些点都有其对应的在屏幕上的坐 标值和时间戳(event.getEventTime());那么,这些点是如何组成Gesture的呢?针对这个问题,通过对Android手势源码的 浅析来寻求答案;

下图总体上大概描述了Gesture的形成结构,如下:

%title插图%num

从上图描述的类关系中,可以知道:

1. 触摸屏上的点首先是通过GesturePoint这个类来描述的,GesturePoint封装点的x,y轴值和时间戳。

2. GesturePoint中封装的点的信息会在GestureStroke类中被拆解处理,点对应的x,y值被拆解存放在GestureStroke的 float类型数组成员points中,而点对应的时间戳则放在long类型成员数组timestamps中。

3. GestureStroke表示一个手势行程(用户手指点下屏幕到手势离开屏幕绘制出的轨迹就是一个手势行程)。一个完整的手势由一个或多个手势行程组成(单笔画或多笔画绘制手势)

4. Gesture由单个或多个GestureStroke组成,Gesture类中的mStrokeBuffer成员为ArrayList类型集合,存放的是GestureStroke;

   二. Gesture的形成过程:

当我们在GestureOverlayView上绘制手势时,会调用GestureOverlayView的touchDown、touchMove、touchUp方法,然后将通过这个三个方法捕获到的形成手势的多数个点组成Gesture。如下代码:

 

  1. public class GestureOverlayView extends FrameLayout {  
  2.     private void touchDown(MotionEvent event) {  
  3.     …
  4.         mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));  
  5.     …
  6.     }
  7.     private Rect touchMove(MotionEvent event) {  
  8.     …
  9.         mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));  
  10.     …
  11.     }
  12.     private void touchUp(MotionEvent event, boolean cancel) {  
  13.     …
  14.          mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));  
  15.     …
  16.     }
  17. }

—->通过上面的代码可知,当用户正在绘制手势时,会调用touchDown、touchMove,执行mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())),实现将点的x、y、event.getEventTime() 值作为GesturePoint的构造函数的实参创建GesturePoint对象,然后将得到的GesturePoint添加到mStrokeBuffer集合中(mStrokeBuffer为ArrayList<GesturePoint>类型);

GesturePoint的源代码如下:

 

  1. /* 
  2.  * Copyright (C) 2008-2009 The Android Open Source Project 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the “License”); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an “AS IS” BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16. package android.gesture;  
  17. import java.io.DataInputStream;  
  18. import java.io.IOException;  
  19. /** 
  20.  * A timed point of a gesture stroke. Multiple points form a stroke. 
  21.  */  
  22. //一个手势行程的定时点,多个点形成一个手势行程。GesturePoint封装x,y轴和时间戳的值  
  23. public class GesturePoint {  
  24.     public final float x;  
  25.     public final float y;  
  26.     public final long timestamp;  
  27.     public GesturePoint(float x, float y, long t) {  
  28.         this.x = x;  
  29.         this.y = y;  
  30.         timestamp = t;
  31.     }
  32.     //从输入流中读取之前保存在文件中的数据  
  33.     static GesturePoint deserialize(DataInputStream in) throws IOException {  
  34.         // Read X and Y  
  35.         final float x = in.readFloat(); //从输入流中读出对应x轴的坐标值 (来自通过调用GestureStroke的函数serialize保存的,下同)  
  36.         final float y = in.readFloat(); //从输入流中读出对应y轴的坐标值  
  37.         // Read timestamp  
  38.         final long timeStamp = in.readLong(); //从输入流中读出对应的时间戳  
  39.         return new GesturePoint(x, y, timeStamp);  
  40.     }
  41.     @Override  
  42.     public Object clone() {  
  43.         return new GesturePoint(x, y, timestamp);  
  44.     }
  45. }

通过源码可知,在GesturePoint的构造函数中,将传进来的点的各个信息值分别赋值给自身的成员变量x、y、timestamp;所以GesturePoint描述的就是组成完成手势中的一个点元素;而GestureOverlayView中的mStrokeBuffer集合保存着组成手势的多数个点

—->紧接着,当用户完成手势绘制手指离开屏幕时,会调用touchUp,执行 mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer)),实现将之前绘制手势得到的mStrokeBuffer集合作为GestureStroke构造函数的实参创建GestureStroke对象,然后将GestureStroke对象作为mCurrentGesture(Gesture对象)的方法addStroke的实参,实现将GestureStroke添加到Gesture中;

GesturePoint的部分源代码如下:

  1. /** 
  2.  * A gesture stroke started on a touch down and ended on a touch up. A stroke 
  3.  * consists of a sequence of timed points. One or multiple strokes form a gesture. 
  4.  */  
  5. public class GestureStroke {  
  6.     public final float length;  //length为手势行程的长度     
  7.     public final float[] points; //保存组成手势行程的多数个点的x,y坐标值   
  8.     private final long[] timestamps;//保存组成手势行程的多数个点的时间戳  
  9.     /** 
  10.      * A constructor that constructs a gesture stroke from a list of gesture points. 
  11.      *  
  12.      * @param points 
  13.      */  
  14.     public GestureStroke(ArrayList<GesturePoint> points) {  
  15.         final int count = points.size();  
  16.         final float[] tmpPoints = new float[count * 2];  
  17.         final long[] times = new long[count];  
  18.         RectF bx = null;  
  19.         float len = 0;  
  20.         int index = 0;  
  21.         for (int i = 0; i < count; i++) {  
  22.             final GesturePoint p = points.get(i);  
  23.             tmpPoints[i * 2] = p.x; //偶数位置保存x值  
  24.             tmpPoints[i * 2 + 1] = p.y; //奇数位置保存x值  
  25.             times[index] = p.timestamp;
  26.             if (bx == null) {  
  27.                 bx = new RectF();  
  28.                 bx.top = p.y;
  29.                 bx.left = p.x;
  30.                 bx.right = p.x;
  31.                 bx.bottom = p.y;
  32.                 len = 0;  
  33.             } else {  
  34.                 //Math.pow(a,b)为a的b次方,如Maht.pow(3,2)等于9。下面的公式相当于平方和的根号值  
  35.                 len += Math.sqrt(Math.pow(p.x – tmpPoints[(i – 1) * 2], 2)  
  36.                         + Math.pow(p.y – tmpPoints[(i –1 ) * 2 + 1], 2));  
  37.                 //放大bx覆盖到指定的p.x, p.y点  
  38.                 bx.union(p.x, p.y);
  39.             }
  40.             index++;
  41.         }
  42.         timestamps = times;
  43.         this.points = tmpPoints;  
  44.         boundingBox = bx;
  45.         length = len;
  46.     }
  47. }

通过上面的代码可知,当我们创建GestureStroke的对象时,会执行GestureStroke的构造函数。而在GestureStroke的构造函数中,实现将传进来的mStrokeBuffer集合中封存的多个点进行遍历拆解出来,然后分别赋值给GestureStroke的数组成员变量points,timestamps,同时也根据点的坐标值计算出手势行程的长度length;

—->接着,将创建得到的GestureStroke对象通过调用Gesture的addStroke方法添加到Gesture类的mStrokes中,Gesture的addStroke方法源码实现如下:

  1. /** 
  2.  * A gesture is a hand-drawn shape on a touch screen. It can have one or multiple strokes. 
  3.  * Each stroke is a sequence of timed points. A user-defined gesture can be recognized by  
  4.  * a GestureLibrary.  
  5.  */  
  6. /*手势时是触摸屏上手势绘制的形状,它可以单笔画或者多笔画, 
  7.  * 每一个笔画是一个计时点序列,用户绘制定义的手势可以通过GestureLibrary来识别 
  8.  */  
  9. public class Gesture implements Parcelable {  
  10.     private final ArrayList<GestureStroke> mStrokes = new ArrayList<GestureStroke>();  
  11.     …
  12.     /** 
  13.      * Adds a stroke to the gesture. 
  14.      *  
  15.      * @param stroke 
  16.      */  
  17.     public void addStroke(GestureStroke stroke) {  
  18.         mStrokes.add(stroke);
  19.         …
  20.     }
  21.     …
  22. }

所以,在Gesture的成员mStrokes中存放着是用户在触摸屏上绘制形成的当前手势相关信息。在Gesture中会根据得到的mStrokes 中这些信息去进行一些重要的处理,如将其序列化存储(serialize)、手势转化成bitmap显示(toBitmap)、还原手势的绘制路径 (toPath)等;

*后,针对手势组成类之间的关系进行一个小结:

1). GesturePoint: 描述用户手指在屏幕位置的一个定时点,封装用户手指在屏幕上的点坐标值以及时间戳,时间戳由event.getEventTime()决定。

2). GestureStroke:描述用户手指在屏幕上滑动到手指离开屏幕时所产生的轨迹线(由多个时间序列点形成),一个GestureStroke由多个GesturePoint组成。

3). Gesture:实现Parcelable接口,描述用户完成绘制的完整手势,一个Gesture由单个或多个GestureStroke组成。手势绘制可通过GestureOverlayView.setGestureStrokeType(inttype)来设置单笔和多笔画。

 

Android 手势识别类 ( 二 ) GestureDetector 源码浅析

Android 手势识别类 ( 二 ) GestureDetector 源码浅析

  前言:Android 关于手势的操作提供两种形式:一种是针对用户手指在屏幕上划出的动作而进行移动的检测,这些手势的检测通过android提供的监听器来实现;另一种是用 户手指在屏幕上滑动而形成一定的不规则的几何图形(即为多个持续触摸事件在屏幕形成特定的形状);本文主要是针对第二种手势的绘制原理进行浅析,我们姑且 称它为输入法手势;

    一. 输入法手势

在Android源码中,谷歌提供了相关的手势库源码,供给开发者丰富多彩的接口调用实现;这些提供相关接口的类所在的源码路径为frameworks/base/core/java/android/gesture;

如下图gesture文件中的相关类:

%title插图%num

 

绘制手势需要一个视图界面平台,而这个视图界面平台由GestureOverlayView这个类来实现,该类继承FrameLayout容器视图类。所以,当我们在手机屏幕上画手势时,GestureOverlayView主要负责显示和处理手指在屏幕上滑动所形成的手势。

以下举一个简单的Demo来说明如何通过GestureOverlayView实现在屏幕上绘制手势;

1). main.xml文件代码如下:

 

  1. <?xml version=“1.0” encoding=”utf-8″?>  
  2. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”  
  3.     android:layout_width=“fill_parent”  
  4.     android:layout_height=“fill_parent”  
  5.     android:orientation=“vertical” >  
  6.    <android.gesture.GestureOverlayView   
  7.        android:id=“@+id/gesture”  
  8.        android:layout_width=“fill_parent”  
  9.        android:layout_height=“fill_parent”  
  10.        >      
  11.    </android.gesture.GestureOverlayView>  
  12. </LinearLayout>  

很简单,添加一个android.gesture.GestureOverlayView的布局组件;

2). 加载布局文件和实现手势绘制的Actitivty代码如下:

 

  1. package com.stevenhu.hu.dgt;  
  2. import android.app.Activity;  
  3. import android.gesture.Gesture;  
  4. import android.gesture.GestureOverlayView;  
  5. import android.gesture.GestureOverlayView.OnGesturePerformedListener;  
  6. import android.gesture.GestureOverlayView.OnGesturingListener;  
  7. import android.os.Bundle;  
  8. import android.widget.Toast;  
  9. public class DrawGestureTest extends Activity implements OnGesturePerformedListener, OnGesturingListener  
  10. {
  11.     private GestureOverlayView mDrawGestureView;  
  12.     /** Called when the activity is first created. */  
  13.     @Override  
  14.     public void onCreate(Bundle savedInstanceState)  
  15.     {
  16.         super.onCreate(savedInstanceState);  
  17.         setContentView(R.layout.main);
  18.         mDrawGestureView = (GestureOverlayView)findViewById(R.id.gesture);
  19.         //设置手势可多笔画绘制,默认情况为单笔画绘制  
  20.         mDrawGestureView.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);
  21.         //设置手势的颜色(蓝色)  
  22.         mDrawGestureView.setGestureColor(gestureColor(R.color.gestureColor));
  23.         //设置还没未能形成手势绘制是的颜色(红色)  
  24.         mDrawGestureView.setUncertainGestureColor(gestureColor(R.color.ungestureColor));
  25.         //设置手势的粗细  
  26.         mDrawGestureView.setGestureStrokeWidth(4);  
  27.         /*手势绘制完成后淡出屏幕的时间间隔,即绘制完手指离开屏幕后相隔多长时间手势从屏幕上消失; 
  28.          * 可以理解为手势绘制完成手指离开屏幕后到调用onGesturePerformed的时间间隔 
  29.          * 默认值为420毫秒,这里设置为2秒 
  30.          */  
  31.         mDrawGestureView.setFadeOffset(2000);  
  32.         //绑定监听器  
  33.         mDrawGestureView.addOnGesturePerformedListener(this);  
  34.         mDrawGestureView.addOnGesturingListener(this);  
  35.     }
  36.     //手势绘制完成时调用  
  37.     @Override  
  38.     public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture)   
  39.     {
  40.         // TODO Auto-generated method stub  
  41.         showMessage(“手势绘制完成”);  
  42.     }
  43.     private int gestureColor(int resId)  
  44.     {
  45.         return getResources().getColor(resId);  
  46.     }
  47.     private void showMessage(String s)  
  48.     {
  49.         Toast.makeText(this, s, Toast.LENGTH_SHORT).show();  
  50.     }
  51.     //结束正在绘制手势时调用(手势绘制完成时一般是先调用它在调用onGesturePerformed)  
  52.     @Override  
  53.     public void onGesturingEnded(GestureOverlayView overlay)   
  54.     {
  55.         // TODO Auto-generated method stub  
  56.         showMessage(“结束正在绘制手势”);  
  57.     }
  58.     //正在绘制手势时调用  
  59.     @Override  
  60.     public void onGesturingStarted(GestureOverlayView overlay)   
  61.     {
  62.         // TODO Auto-generated method stub  
  63.         showMessage(“正在绘制手势”);  
  64.     }
  65.     @Override  
  66.     protected void onDestroy()   
  67.     {
  68.         // TODO Auto-generated method stub  
  69.         super.onDestroy();  
  70.         //移除绑定的监听器  
  71.         mDrawGestureView.removeOnGesturePerformedListener(this);  
  72.         mDrawGestureView.removeOnGesturingListener(this);  
  73.     }
  74. }

示例代码下载链接地址:http://download.csdn.net/detail/stevenhu_223/5789777
通过上面的Demo可知,要想实现绘制和监听操作手势,GestureOverlayView是必不可少的,GestureOverlayView为何方神圣
,它是如何实现手势的绘制和监听操作的,接下来将对它进行浅析。

   二. GestureOverlayView类浅析

其实手势的绘制原理和前篇<<Android中Path类的lineTo方法和quadTo方法画线的区别>>中绘制轨迹线的原理差不多,只不过在GestureOverlayView中的处理相对比较复杂;

GestureOverlayView继承FrameLayout,所以它也是ViewGroup类型(继承 View),GestureOverlayView重写View的dispatchTouchEvent方法。所以,我们手指在屏幕上触摸滑动时,会调用 GestureOverlayView的dispatchTouchEvent方法;代码如下:

 

  1. public class GestureOverlayView extends FrameLayout {  
  2.  @Override  
  3.     public boolean dispatchTouchEvent(MotionEvent event) {  
  4.         if (isEnabled()) {  
  5.             final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&  
  6.                     mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&  
  7.                     mInterceptEvents;
  8.             processEvent(event);
  9.             if (cancelDispatch) {  
  10.                 event.setAction(MotionEvent.ACTION_CANCEL);
  11.             }
  12.             super.dispatchTouchEvent(event);  
  13.             return true;  
  14.         }
  15.         return super.dispatchTouchEvent(event);  
  16.     }
  17. }

isEnabled()得到当前视图的enable状态,若当前视图的enable状态为true,则继续执行processEvent(event),传入参数为对应的滑动事件。

—-> 我们接着继续跟踪processEvent方法,代码如下:

 

  1.  private boolean processEvent(MotionEvent event) {  
  2.         switch (event.getAction()) {  
  3.             case MotionEvent.ACTION_DOWN:  
  4.                 touchDown(event);
  5.                 invalidate();
  6.                 return true;  
  7.             case MotionEvent.ACTION_MOVE:  
  8.                 if (mIsListeningForGestures) {  
  9.                     Rect rect = touchMove(event);
  10.                     if (rect != null) {  
  11.                         invalidate(rect);
  12.                     }
  13.                     return true;  
  14.                 }
  15.                 break;  
  16.             case MotionEvent.ACTION_UP:  
  17.                 if (mIsListeningForGestures) {  
  18.                     touchUp(event, false);  
  19.                     invalidate();
  20.                     return true;  
  21.                 }
  22.                 break;  
  23.             case MotionEvent.ACTION_CANCEL:  
  24.                 if (mIsListeningForGestures) {  
  25.                     touchUp(event, true);  
  26.                     invalidate();
  27.                     return true;  
  28.                 }
  29.         }
  30.         return false;  
  31.     }

在processEvent方法中会根据用户手指对屏幕操作的MotionEvent进行处理:

1). 当MotionEvent事件为ACTION_DOWN时,调用touchDown(MotionEvent event)方法;

2). 当MotionEvent事件为ACTION_MOVE,且mIsListeningForGestures为true时(执行touchDown时赋值为true),调用touchMove(MotionEvent event)方法;

3). 当MotionEvent事件为ACTION_UP,且mIsListeningForGestures为true时,调用touchUp(MotionEvent event, boolean cancel)方法;mIsListeningForGestures在执行touchUp时赋值为false;

4). 当MotionEvent事件为ACTION_CANCEL,且mIsListeningForGestures为true时,调用touchUp(MotionEvent event, boolean cancel)方法;
接下来逐步分析以上分发处理MotionEvent事件的各个函数的实现:

—->touchDown(MotionEvent event),当用户手指点下屏幕时调用该方法,码如下:

 

  1.  private void touchDown(MotionEvent event) {  
  2.         mIsListeningForGestures = true;  
  3.         float x = event.getX();  
  4.         float y = event.getY();  
  5.         mX = x;
  6.         mY = y;
  7.         mTotalLength = 0;  
  8.         mIsGesturing = false;  
  9.         if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {  
  10.             if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);  
  11.             mResetGesture = false;  
  12.             mCurrentGesture = null;  
  13.             mPath.rewind();
  14.         } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {  
  15.             if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);  
  16.         }
  17.         // if there is fading out going on, stop it.  
  18.         //如果手势已正在淡出,则停止它    
  19.         if (mFadingHasStarted) {  
  20.             cancelClearAnimation();
  21.         } else if (mIsFadingOut) {  
  22.             setPaintAlpha(255);  
  23.             mIsFadingOut = false;  
  24.             mFadingHasStarted = false;  
  25.             removeCallbacks(mFadingOut);
  26.         }
  27.         if (mCurrentGesture == null) {  
  28.             mCurrentGesture = new Gesture();  
  29.         }
  30.         mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));  
  31.         mPath.moveTo(x, y);
  32.         //mInvalidateExtraBorder值由设置手势画笔粗细值决定  
  33.         final int border = mInvalidateExtraBorder;  
  34.         mInvalidRect.set((int) x – border, (int) y – border, (int) x + border, (int) y + border);  
  35.         mCurveEndX = x;
  36.         mCurveEndY = y;
  37.         // pass the event to handlers  
  38.         final ArrayList<OnGestureListener> listeners = mOnGestureListeners;  
  39.         final int count = listeners.size();  
  40.         for (int i = 0; i < count; i++) {  
  41.             listeners.get(i).onGestureStarted(this, event);  
  42.         }
  43.     }

在touchDown中,实现处理当用户手指在点下屏幕时的一些操作,这些操作包括:

1). 获取用户手指点下屏幕时所在的坐标值x,y,同时将它们分别赋值给全局变量mX,mY;mTotalLength变量代表绘制手势的总长度,在调用 touchDown时,手势还没绘制,所以mTotalLength为0;mIsGesturing描述是否正在绘制手势,为false表示不是正在绘制 手势;
2). 根据一些条件判断,设置画笔颜色,处理手势画笔的相关状态,以及创建Gesture对象等。

3). 将1)得到的x,y坐标值和event.getEventTime()的值作为GesturePoint构造函数的实参创建GesturePoint对象,并将该对象添加进mStrokeBuffer数组集合。

4). 将1)得到的x,y坐标值作为mPath画笔路径的初始点。

5). 遍历存放OnGestureListener的集合listeners,调用实现OnGestureListener接口的onGestureStarted()方法;

—->touchMove(MotionEvent event),当用户手指在屏幕上滑动时调用该方法,码如下:

 

  1. private Rect touchMove(MotionEvent event) {  
  2.         //更新区域  
  3.         Rect areaToRefresh = null;  
  4.         final float x = event.getX();  
  5.         final float y = event.getY();  
  6.         final float previousX = mX;  
  7.         final float previousY = mY;  
  8.         final float dx = Math.abs(x – previousX);  
  9.         final float dy = Math.abs(y – previousY);  
  10.         //手势在屏幕滑动的两点之间的距离大于GestureStroke.TOUCH_TOLERANCE的值,则显示手势的绘制  
  11.         if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {  
  12.             areaToRefresh = mInvalidRect;
  13.             // start with the curve end  
  14.             final int border = mInvalidateExtraBorder;  
  15.             areaToRefresh.set((int) mCurveEndX – border, (int) mCurveEndY – border,  
  16.                     (int) mCurveEndX + border, (int) mCurveEndY + border);  
  17.             //设置贝塞尔曲线的操作点为起点和终点的一半  
  18.             float cX = mCurveEndX = (x + previousX) / 2;  
  19.             float cY = mCurveEndY = (y + previousY) / 2;  
  20.             //二次贝塞尔,实现平滑曲线;previousX, previousY为操作点,cX, cY为终点  
  21.             mPath.quadTo(previousX, previousY, cX, cY);
  22.             // union with the control point of the new curve  
  23.             /*areaToRefresh矩形扩大了border(宽和高扩大了两倍border), 
  24.              * border值由设置手势画笔粗细值决定 
  25.              */  
  26.             areaToRefresh.union((int) previousX – border, (int) previousY – border,  
  27.                     (int) previousX + border, (int) previousY + border);  
  28.             // union with the end point of the new curve  
  29.             areaToRefresh.union((int) cX – border, (int) cY – border,  
  30.                     (int) cX + border, (int) cY + border);  
  31.             //第二次执行时,*次结束调用的坐标值将作为第二次调用的初始坐标值  
  32.             mX = x;
  33.             mY = y;
  34.             mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));  
  35.             //当调用addOnGesturePerformedListener添加手势完成调用的监听器时,mHandleGestureActions为true;  
  36.             if (mHandleGestureActions && !mIsGesturing) {  
  37.                 mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);  
  38.                 if (mTotalLength > mGestureStrokeLengthThreshold) {  
  39.                     final OrientedBoundingBox box =  
  40.                             GestureUtils.computeOrientedBoundingBox(mStrokeBuffer);
  41.                     float angle = Math.abs(box.orientation);  
  42.                     if (angle > 90) {  
  43.                         angle = 180 – angle;  
  44.                     }
  45.                     /*这个条件成立时,说明所手势绘制已经在进行 
  46.                      */  
  47.                     if (box.squareness > mGestureStrokeSquarenessTreshold ||  
  48.                             (mOrientation == ORIENTATION_VERTICAL ?
  49.                                     angle < mGestureStrokeAngleThreshold :
  50.                                     angle > mGestureStrokeAngleThreshold)) {
  51.                         mIsGesturing = true;  
  52.                         //手势尚未形成的显示颜色  
  53.                         setCurrentColor(mCertainGestureColor);
  54.                         final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;  
  55.                         int count = listeners.size();  
  56.                         for (int i = 0; i < count; i++) {  
  57.                             listeners.get(i).onGesturingStarted(this);  
  58.                         }
  59.                     }
  60.                 }
  61.             }
  62.             // pass the event to handlers  
  63.             final ArrayList<OnGestureListener> listeners = mOnGestureListeners;  
  64.             final int count = listeners.size();  
  65.             for (int i = 0; i < count; i++) {  
  66.                 listeners.get(i).onGesture(this, event);  
  67.             }
  68.         }
  69.         return areaToRefresh;  
  70.     }

touchMove方法中主要有以下功能的实现:
1). touchMove方法返回值类型为Rect(定义一个矩形区域),若返回值不会空,则调用invalidate(Rectrect)刷新;

2). 得到当前的手指滑动所在屏幕位置的x,y坐标值,将x,y值与调用touchDown()时得到的x,y值相减后取*对值,得到偏移量dx,dy;

 

3). dx或dy大于指定的GestureStroke.TOUCH_TOLERANCE时(默认值为3),执行画笔绘制手势的实现流程代码。

4). mPath画笔路径调用quadTo()方法执行贝塞尔曲线计算,实现得到平滑曲线。

 

5). areaToRefresh矩形区域负责根据手势绘制控制点和结束点的位置不断更新,画出手势画笔轨迹(每次调用touchMove()时,areaToRefresh逐点更新从而汇成一定轨迹的几何图形,即手势的雏形)。

6). 将第二步得到x,y坐标值和event.getEventTime()的值作为GesturePoint构造函数的实参创建GesturePoint对 象,并将该对象添加进mStrokeBuffer数组集合。(保存用户在屏幕上绘制形成手势的相关信息)

 

7). 当调用GestureOverlayView的addOnGesturePerformedListener方法添加监听器 OnGesturePerformedListener时,mHandleGestureActions为true,这时候会执行计算移动所得的这些点集 的*小边界框,然后根据这个*小边界框进行一些条件判断,进而设置mIsGesturering为true,以及设置手势尚未形成绘制手势的显示颜色。

8). touchMove()的*后,遍历存放OnGestureListener接口的集合listeners,调用实现OnGestureListener接口的onGesture方法。

—->touchUp(MotionEvent event, boolean cancel),当用户手指离开屏幕或MotionEvent 事件取消时调用该方法,码如下:

 

  1. private void touchUp(MotionEvent event, boolean cancel) {  
  2.         mIsListeningForGestures = false;  
  3.         // A gesture wasn’t started or was cancelled  
  4.         if (mCurrentGesture != null) {  
  5.             // add the stroke to the current gesture  
  6.             /*将之前调用touchDonw和touchMove收集得到GesturePoint的组成的数组集合mStrokeBuffer, 
  7.              * 做为GestureStroke构造函数的实参创建GestureStroke对象, 
  8.              * 然后将GestureStroke对象通过调用addStroke方法添加到mCurrentGesture中 
  9.              */  
  10.             mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));  
  11.             if (!cancel) {  
  12.                 // pass the event to handlers  
  13.                 final ArrayList<OnGestureListener> listeners = mOnGestureListeners;  
  14.                 int count = listeners.size();  
  15.                 for (int i = 0; i < count; i++) {  
  16.                     listeners.get(i).onGestureEnded(this, event);  
  17.                 }
  18.                 /*当调用addOnGesturePerformedListener方法时,mHandleGestureActions为true; 
  19.                  * mFadeEnabled默认值为true,可通过setFadeEnabled函数设值 
  20.                  */  
  21.                 clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
  22.                         false);  
  23.             } else {  
  24.                 cancelGesture(event);
  25.             }
  26.         } else {  
  27.             cancelGesture(event);
  28.         }
  29.         mStrokeBuffer.clear();
  30.         mPreviousWasGesturing = mIsGesturing;
  31.         mIsGesturing = false;  
  32.         final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;  
  33.         int count = listeners.size();  
  34.         for (int i = 0; i < count; i++) {  
  35.             listeners.get(i).onGesturingEnded(this);  
  36.         }
  37.     }

touchUp方法中主要有以下功能的实现:
1). 首先将mIsListeningForGesture赋值为false;

 

2). 判断当前是否存在mCurrentGesture(Gesture类型),该变量在执行touchDown方法时创建Gesture对象赋值的,也可以通 过调用setGesture方法赋值;(mCurrentGesture描述的就是当前用户绘制形成的整个手势)

3). 若mCurrentGesture不为空,则将之前调用touchDonw和touchMove收集得到的GesturePoint组成的数组集合 mStrokeBuffer做为GestureStroke构造函数的实参,创建GestureStroke对象。然后将GestureStroke对象 通过调用addStroke方法添加到mCurrentGesture中;

4). 若touchUp方法的第二个参数为false(即执行ACTION_UP事件时),则遍历存放OnGestureListener的集合,调用实现该接 口的onGestureEnded()方法。接着调用clear方法,实现将当前绘制形成的手势清除(即手势淡出屏幕;手指离开屏幕时到手势淡出屏幕,这 期间是有时间间隔的,且这个时间间隔也是可以设置);

5). 若touchUp()方法的第二个参数为true(即执行ACTION_CANCEL事件时),调用cancelGesture()方法。在该方法中:首 先遍历存放OnGestureListener的集合,调用实现该接口的onGestureCancelled()方法,接着调用clear()方法实现 回收mCurrentGesture对象、清除画笔等淡出屏幕处理;

—->上面4)中,当touchUp方法的cancel参数为false时,通过调用clear(boolean animated, boolean fireActionPerformed, boolean immediate)处理手势淡出屏幕,我们来看看这个方法的实现,代码如下:

 

  1. private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {  
  2.         setPaintAlpha(255);  
  3.         removeCallbacks(mFadingOut);
  4.         mResetGesture = false;  
  5.         mFadingOut.fireActionPerformed = fireActionPerformed;
  6.         mFadingOut.resetMultipleStrokes = false;  
  7.         if (animated && mCurrentGesture != null) { //调用addOnGesturePerformedListener时animated为true  
  8.             mFadingAlpha = 1.0f;  
  9.             mIsFadingOut = true;  
  10.             mFadingHasStarted = false;  
  11.             /*mFadeOffset定义手势淡出屏幕的时间间隔, 
  12.              * 默认值420,可通过setFadeOffset函数设置 
  13.              */  
  14.             mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
  15.             postDelayed(mFadingOut, mFadeOffset);
  16.         } else {  
  17.             mFadingAlpha = 1.0f;  
  18.             mIsFadingOut = false;  
  19.             mFadingHasStarted = false;  
  20.             if (immediate) {  
  21.                 mCurrentGesture = null;  
  22.                 mPath.rewind();
  23.                 invalidate();
  24.             } else if (fireActionPerformed) {  
  25.                 postDelayed(mFadingOut, mFadeOffset);
  26.             } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {  
  27.                 mFadingOut.resetMultipleStrokes = true;  
  28.                 postDelayed(mFadingOut, mFadeOffset);
  29.             } else {  
  30.                 mCurrentGesture = null;  
  31.                 mPath.rewind();
  32.                 invalidate();
  33.             }
  34.         }
  35.     }

通过上面的代码,我们知道,在clear函数中,会通过传入的实参来决定如何去进一步处理手势的淡出,有两种处理方式:

1. 调用mPath.rewind(),将绘制手势的重置清除,然后调用invalidate();

2. 调用postDelayed(mFadingOut, mFadeOffset),到主线程中处理,mFadeOffset就是决定手势淡出屏幕的时间间隔;

我们针对第二种在主线程中处理的方式继续跟踪解析代码,mFadingOut是FadeOutRunnable对象,FadeOutRunnable继承Runnable类,该类的实现代码如下:

 

  1. /*处理手势淡出; 
  2.      * 手势淡出的条件: 
  3.      * 1.前面一次画完手势,且画完的同时没有调用onGesturePerformed, 
  4.      *   则当用户再次画手势时,前面画出的保留在屏幕上的手势将淡出; 
  5.      * 2.当画完手势,且添加OnGesturePerformedListener监听器时, 
  6.      *   在完成手势,调用onGesturePerformed时,将手势轨迹画笔淡出 
  7.      */  
  8.     private class FadeOutRunnable implements Runnable {  
  9.         //调用addOnGesturePerformedListener时为true;  
  10.         boolean fireActionPerformed;  
  11.         //手势设置为多笔画绘制时为true;  
  12.         boolean resetMultipleStrokes;  
  13.         public void run() {  
  14.             if (mIsFadingOut) { //fireActionPerformed为true且mCurrentGesture不为空是成立  
  15.                 final long now = AnimationUtils.currentAnimationTimeMillis();  
  16.                 final long duration = now – mFadingStart;  
  17.                 //mFadeDuration默认值为150  
  18.                 if (duration > mFadeDuration) {  
  19.                     if (fireActionPerformed) {  
  20.                         //调用onGesturePerformed方法  
  21.                         fireOnGesturePerformed();
  22.                     }
  23.                     mPreviousWasGesturing = false;  
  24.                     mIsFadingOut = false;  
  25.                     mFadingHasStarted = false;  
  26.                     mPath.rewind();
  27.                     mCurrentGesture = null;  
  28.                     setPaintAlpha(255);  
  29.                 } else {  
  30.                     mFadingHasStarted = true;  
  31.                     float interpolatedTime = Math.max(0.0f,  
  32.                             Math.min(1.0f, duration / (float) mFadeDuration));  
  33.                     mFadingAlpha = 1.0f – mInterpolator.getInterpolation(interpolatedTime);  
  34.                     setPaintAlpha((int) (255 * mFadingAlpha));  
  35.                     //FADE_ANIMATION_RATE默认值为16  
  36.                     postDelayed(this, FADE_ANIMATION_RATE);  
  37.                 }
  38.             } else if (resetMultipleStrokes) { //fireActionPerformed为false且手势为多笔画绘制时成立  
  39.                 mResetGesture = true;  
  40.             } else {  
  41.                 //调用实现监听器OnGesturePerformedListener的onGesturePerformed方法  
  42.                 fireOnGesturePerformed();
  43.                 mFadingHasStarted = false;  
  44.                 mPath.rewind();
  45.                 mCurrentGesture = null;  
  46.                 mPreviousWasGesturing = false;  
  47.                 setPaintAlpha(255);  
  48.             }
  49.             invalidate();
  50.         }
  51.     }

值得注意的是,在主线程中处理手势淡出屏幕,当我们绑定了监听器OnGesturePerformedListener,手势淡出屏幕时会调用fireOnGesturePerformed方法,该方法实现遍历存放OnGesturePerformedListener的集合actionListeners,进而调用实现OnGesturePerformedListener接口的函数onGesturePerformed,代码如下:

  1. private void fireOnGesturePerformed() {  
  2.         final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners;  
  3.         final int count = actionListeners.size();  
  4.         for (int i = 0; i < count; i++) {  
  5.             actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);  
  6.         }
  7.     }

*后,有一点值得注意, 当我们手指在触摸屏上滑动时,在processEvent方法中,每次执行完touchDown、touchMove方法后都会调用 invalidate()、invalidate(rect)进行不断的刷新,那么这时候就调用draw方法将用户在触摸屏上绘制的手势轨迹显示出来,代 码如下:

 

  1. @Override  
  2.     public void draw(Canvas canvas) {  
  3.         super.draw(canvas);  
  4.         if (mCurrentGesture != null && mGestureVisible) {  
  5.             canvas.drawPath(mPath, mGesturePaint);
  6.         }
  7.     }

至此,关于实现手势绘制的视图平台类GestureOverlayView的浅析就结束了!

Android 手势识别类 ( 一 ) GestureDetector 基本介绍

Android 手势识别类 ( 一 ) GestureDetector 基本介绍

为了加强鼠标响应事件,Android提供了GestureDetector手势识别类。通过GestureDetector.OnGestureListener来获取当前被触发的操作手势(Single Tap Up、Show Press、Long Press、Scroll、Down、Fling),具体包括以下几种:

boolean  onDoubleTap(MotionEvent e)
解释:双击的第二下Touch down时触发
boolean  onDoubleTapEvent(MotionEvent e)
解释:双击的第二下Touch down和up都会触发,可用e.getAction()区分。
boolean  onDown(MotionEvent e)
解释:Touch down时触发
boolean  onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
解释:Touch了滑动一点距离后,up时触发。
void  onLongPress(MotionEvent e)
解释:Touch了不移动一直Touch down时触发
boolean  onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
解释:Touch了滑动时触发。
void  onShowPress(MotionEvent e)
解释:Touch了还没有滑动时触发
(与onDown,onLongPress比较
onDown只要Touch down一定立刻触发。
而Touchdown后过一会没有滑动先触发onShowPress再是onLongPress。
所以Touchdown后一直不滑动,onDown->onShowPress->onLongPress这个顺序触发。

boolean  onSingleTapConfirmed(MotionEvent e)
boolean  onSingleTapUp(MotionEvent e)
解释:上面这两个函数都是在touch down后又没有滑动(onScroll),又没有长按(onLongPress),然后Touchup时触发。

点击一下非常快的(不滑动)Touchup:
onDown->onSingleTapUp->onSingleTapConfirmed
点击一下稍微慢点的(不滑动)Touchup:
onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed

使用GestureDetector需要在View中重写onTouchEvent事件,例如:

  1. GestureDetector mGesture = null;  
  2. @Override  
  3.     public boolean onTouch(View v, MotionEvent event)  
  4.     {
  5.         // TODO Auto-generated method stub  
  6.         return mGesture.onTouchEvent(event);  
  7.     }

详细的测试例子如下:

  1. package com.jiubang.android.gesturetest;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.GestureDetector;
  6. import android.view.MotionEvent;
  7. import android.view.View;
  8. import android.view.GestureDetector.SimpleOnGestureListener;
  9. import android.view.View.OnTouchListener;
  10. import android.widget.Button;
  11. public class GestureActivity extends Activity  
  12.             implements OnTouchListener
  13. {
  14.     private Button mButton = null;  
  15.     GestureDetector mGesture = null;
  16.     /** Called when the activity is first created. */  
  17.     @Override
  18.     public void onCreate(Bundle savedInstanceState) {  
  19.         super.onCreate(savedInstanceState);
  20.         setContentView(R.layout.main);
  21.         Log.i(“TEST”, “onCreate”);  
  22.         mButton = (Button)findViewById(R.id.button1);
  23.         mButton.setOnTouchListener(this);  
  24.         mGesture = new GestureDetector(this, new GestureListener());  
  25.     }
  26.     @Override
  27.     public boolean onTouch(View v, MotionEvent event)  
  28.     {
  29.         // TODO Auto-generated method stub  
  30.         return mGesture.onTouchEvent(event);  
  31.     }
  32.     class GestureListener extends SimpleOnGestureListener  
  33.     {
  34.         @Override
  35.         public boolean onDoubleTap(MotionEvent e)  
  36.         {
  37.             // TODO Auto-generated method stub  
  38.             Log.i(“TEST”, “onDoubleTap”);  
  39.             return super.onDoubleTap(e);  
  40.         }
  41.         @Override
  42.         public boolean onDown(MotionEvent e)  
  43.         {
  44.             // TODO Auto-generated method stub  
  45.             Log.i(“TEST”, “onDown”);  
  46.             return super.onDown(e);  
  47.         }
  48.         @Override
  49.         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,  
  50.                 float velocityY)  
  51.         {
  52.             // TODO Auto-generated method stub  
  53.             Log.i(“TEST”, “onFling:velocityX = ” + velocityX + ” velocityY” + velocityY);  
  54.             return super.onFling(e1, e2, velocityX, velocityY);  
  55.         }
  56.         @Override
  57.         public void onLongPress(MotionEvent e)  
  58.         {
  59.             // TODO Auto-generated method stub  
  60.             Log.i(“TEST”, “onLongPress”);  
  61.             super.onLongPress(e);
  62.         }
  63.         @Override
  64.         public boolean onScroll(MotionEvent e1, MotionEvent e2,  
  65.                 float distanceX, float distanceY)  
  66.         {
  67.             // TODO Auto-generated method stub  
  68.             Log.i(“TEST”, “onScroll:distanceX = ” + distanceX + ” distanceY = ” + distanceY);  
  69.             return super.onScroll(e1, e2, distanceX, distanceY);  
  70.         }
  71.         @Override
  72.         public boolean onSingleTapUp(MotionEvent e)  
  73.         {
  74.             // TODO Auto-generated method stub  
  75.             Log.i(“TEST”, “onSingleTapUp”);  
  76.             return super.onSingleTapUp(e);  
  77.         }
  78.     }
  79. }

 

//以上部分内容选自网络

求一种适合在服务器上跑的 BT 下载软件,需要支持导出下载任务

服务器用的人挺多,经常被搞坏,要重装 所以,未完成的下载任务和下载到一半的文件,是否可以导出到另一台电脑上继续下载呢? 有这样的 bt 下载软件呢? utorrent 软件这方面的支持太弱了,以前迅雷好像有导入为完成任务的功能,不过迅雷是个流氓软件,很久不用了

7 条回复 • 2016-05-23 07:14:54 +08:00
Tink 1
Tink 2016-05-21 05:20:38 +08:00
transmission
zhjits 2
zhjits 2016-05-21 09:49:42 +08:00
是个 BT 软件都支持的吧,你只要把下了一半的文件导出来,导入的时候指定文件位置让它重新扫一遍就好了。
ryd994 3
ryd994 2016-05-21 10:24:43 +08:00 via Android
只要有原始种子,都可以啊
arrowna 4
arrowna 2016-05-22 23:37:14 +08:00
@ryd994 重新选择下载,把下载目录设置在未下载完的目录?
ryd994 5
ryd994 2016-05-23 05:09:22 +08:00 via Android
@arrowna 一般是这样,但是有些软件喜欢把没下完的文件名家加.part ,这样和其他软件会接不上
除此以外没有问题
完全分配磁盘空间而不是紧凑分配之类的,可能也有点关系
arrowna 6
arrowna 2016-05-23 07:03:38 +08:00
@ryd994 utorrent 好像不适合把未完成下载任务迁移到另一台电脑上下载。。。
arrowna 7
arrowna 2016-05-23 07:14:54 +08:00
@Tink 你在什么系统上用 transmission ?

云服务器运维求助

LZ 小白一枚 目前阿里云服务器挂了 AMH 的虚拟面板 有一个并发比较大的虚拟主机经常打不开。 服务器配置: 8 核 16G 内存 并发大的虚拟主机数据库挂的是阿里云 RDS ,已配置读写分离 but 已然无法解决问题。

愿付费求 V2 大神指导

万分感谢

并发 虚拟 服务器 阿里云5 条回复 • 2016-05-30 14:01:18 +08:00
esile 1
esile 2016-05-30 13:13:00 +08:00 via iPhone ❤️ 1
需要钱解决的问题 v2 也帮不了你
yexm0 2
yexm0 2016-05-30 13:23:35 +08:00 via Android ❤️ 1
@aliyun123 @aliyunservice
miaosu 3
miaosu 2016-05-30 13:33:21 +08:00 ❤️ 1
你这样的结构按理来说不会出现你说的情况, 这边建议看下你的程序,应该是程序的问题。
qcloud 4
qcloud 2016-05-30 14:00:21 +08:00 ❤️ 1
你可以尝试更换环境,手动配置
ivmm 5
ivmm 2016-05-30 14:01:18 +08:00 ❤️ 1
是什么程序呢?

B 类 ip 地址和 C 类 IP 地址可用的*大网络数量究竟是多少?

请看这里
http://www.tutorialspoint.com/ipv4/ipv4_address_classes.htm

Class B 包含 16384 (2**14) Network addresses , 65534 (2**16-2) Host addresses.
Class C 包含 2097152 (2**21)Network addresses 254 (2**8-2) Host addresses.

请看这里
http://www.vlsm-calc.net/ipclasses.php

Class B 有 163842(2**14-2)Network addresses , 65534 (2**16-2) Host addresses.

Class C 有 20971520(2**21-2) Network addresses a , 254 (2**8-2) Host addresses.
对于 A 类地址,两者的描述完全一致:
class A ,包含的 networks = 126 (2**7 – 2).

class B , networks 是(2**14-2) or (2**14)?
class C , networks 是(2**21-2) or (2**21)?
我在网上查找了很多资料,还有人提供 计算机网络(谢希仁)的说法:
B 类地址的网络号占两个字节,前面两位 10 固定,只剩下 14 位可以进行分配,但是无论怎样取值,都不可能是全 0 或全 1 ,所以不存在减 2 ,但实际上 B 类地址 128.0.0.0 是不指派, B 类网络地址是 128.1.0.0 ,所以 B 类网络可用个数为 2^14 -1;
c 类地址同 B 类, c 类地址 192.0.0.0 也不指派,可用个数为 2^21 -1;

现在,我想弄明白:究竟答案是什么?
128.0 和 192.0.0 不指派,那做什么用了?
addresses class 地址 network13 条回复 • 2016-06-06 02:42:26 +08:00
xiaozhizhu1997 1
xiaozhizhu1997 2016-06-05 17:32:40 +08:00 via Android
早就改用 CIDR 了…
liyvhg 2
liyvhg 2016-06-05 17:35:35 +08:00 via Android
现在除了教科书里面,谁还用 B 类 C 类 A 类区分 IP 地址段。。。。
记得几年前有个新的 RFC 规定 IPv4 可以划分任意长度的子网掩码。例如 255.255.255.252 这种都是可以用的。
按照 ABC 这种分类法,上面的子网掩码是哪一类?~
凭记忆+纯手机打字,如有错误请楼下轻喷。
xingxing09 3
xingxing09 2016-06-05 20:43:16 +08:00 via Android
@liyvhg 这种叫子网划分,可以将原来的网络划分成若干个子网
jasontse 4
jasontse 2016-06-05 21:00:50 +08:00 via Android
分类网络早已不再使用,现在我们说的一个 B 或一个 C 地址块就是指的一个 /16 或 /24 。
Victor215 5
Victor215 2016-06-05 21:04:52 +08:00 via Android
有什么资料可以参考么?*近在仔细的学这块儿~
@jasontse
@liyvhg
gamexg 6
gamexg 2016-06-05 21:10:00 +08:00
tcp/ip 详解 ,里面有掩码、网络地址、广播地址的计算方式。
ip 按位与掩码得到网络地址, ip 按位或(掩码取反) 得到广播地址。
liyvhg 7
liyvhg 2016-06-05 21:50:17 +08:00 via Android
@Victor215 建议楼主不要深究,这是一个已经被淘汰了的技术。
取而代之的标准详见 RFC1518 和 RFC1519 。
liyvhg 8
liyvhg 2016-06-05 21:50:44 +08:00 via Android
@Victor215 另外, 1 楼已经说清楚了~
meiriyitie 9
meiriyitie 2016-06-05 21:55:22 +08:00
CIDR +1
shiji 10
shiji 2016-06-05 22:12:31 +08:00 via Android
楼上说的都很对
楼主要找点专业的教材看,别看这种过时的网页。
notgod 11
notgod 2016-06-06 01:31:55 +08:00 ❤️ 1
CIDR +2

现在我们说的 和机房要的 不需要计算多少
直接和 DC 说 申请 /24
/24 = 256 个 IP = 253 可绑定 IP (-1 网络地址 -1 网关 IP -1 子网掩码)

如果你本意是研究 IP 地址算法的话
可以研究 PHP 的函数 ip2long long2ip

老实说现在这个没什么暖用了
IPv4 用完了 IPv6 根本用不完……
mytsing520 12
mytsing520 2016-06-06 01:39:28 +08:00 via iPhone
老实说不是 192.0 不指派,而是 192.168
shiji 13
shiji 2016-06-06 02:42:26 +08:00 via Android ❤️ 1
/24 = 256 个 IP = 253 可绑定 IP (-1 网络地址 -1 网关 IP -1 子网掩码)

*后那个是 -1 广播地址吧。。子网掩码不是一个 IP

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速