Android的业务逻辑
1.基于监听的事件处理机制
基于监听的事件处理机制,事件源(组件)和时间监听器是分离的,当事件源发生特定事件后,该事件交给事件监听器来处理。其优点是模型分工明确,事件源、事件监听器的两个类分来实现,具有很好的可维护性。
事件监听机制中由事件源,事件,事件监听器三类对象组成处理流程如下:
1. 为某个事件源(组件)设置一个监听器,用于监听用户操作;
2. 用户的操作,触发了事件源(组件)的监听器;
3. 生成了对应的事件对象;
4. 将这个事件源对象作为参数传给事件监听器;
5. 事件监听器对事件对象进行判断,执行对应的事件处理器(对应事件的处理方法);
常用的使用方式:
1. 直接使用匿名类
btnshow = (Button) findViewById(R.id.btnshow);
btnshow.setOnClickListener(new OnClickListener() {
//重写点击事件的处理方法onClick()
@Override
public void onClick(View v) {
//显示Toast信息
Toast.makeText(getApplicationContext(), "你点击了按钮", Toast.LENGTH_SHORT).show();
}
});
2. 使用内部类
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnshow = (Button)findViewById(R.id.btnshow);
//直接new一个内部类对象作为参数
btnshow.setOnClickListener(new BtnClickListener());
}
//定义一个内部类,实现View.OnClickListener接口,并重写onClick()方法
class BtnClickListener implements View.OnClickListener
{
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "按钮被点击了", Toast.LENGTH_SHORT).show();
}
}
3. 直接使用Activity作为事件监听器
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnshow = (Button) findViewById(R.id.btnshow);
//直接写个this
btnshow.setOnClickListener(this);
}
//重写接口中的抽象方法
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "点击了按钮", Toast.LENGTH_SHORT).show();
}
4. 直接绑定到标签
public void myclick(View source)
{
Toast.makeText(getApplicationContext(), "按钮被点击了", Toast.LENGTH_SHORT).show();
}
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮"
android:onClick="myclick"/>
Android为不同的组件通过了不同的监听接口:
View.onClickListener 单击事件的事件监听器必须实现的接口。
View.onCreateContextMenuListener 创建上下文菜单事件的事件监听器必须实现的接口。
View.onFocusChangedListener 焦点改变事件的事件监听器必须实现的接口。
View.onKeyListener 按键事件的时间监听器必须实现的接口。
View.onLangClickListener 长单击事件的事件监听器必须实现的接口。
View.onTouchListener 触摸屏事件的事件监听器必须实现的接口。
2.基于回调的事件处理机制
回调方法
回调函数是一段可执行的代码段,它作为一个参数传递给其他的代码,其作用是在需要的时候方便调用这段(回调函数)代码。
调用程序发出对回调函数的调用后,不等函数执行完毕,立即返回并继续执行。这样,调用程序执和被调用函数同时在执行。
基于回调的事件处理机制简单来说就是不用监听器去实现组件,而是组件(事件源)自身的特定方法去处理事件。也就是说事件源和事件监听器是统一的,当时间源发生特定事件后,该事件还是事件源本身负责处理。
所谓的回调,在实现具有通用性质的应用架构时非常常见:
对于一个具有通用架构的程序来说,程序架构完成整个程序的通用功能、流程,但在某个特定的点上,需要一段业务相关的代码——通用的程序架构无法实现这段代码,那么程序架构会在这个点上留一个“空”。
对于Java程序来说,程序架构在这个点上的留的“空”,可以以一下两种方式存在。1.以接口形式存在:该接口由开发者实现,实现该接口时会实现该接口的方法,那么通用的程序架构就会调用该方法来完成相关业务的处理。
2.以抽象方法(也可以说是非抽象方法)的形式存在:这就是Activity的实现形式,这些特定的点上的方法已经被定义了,如onCreate,onActivityResult等方法,开发者可以选择性地重写这些方法,通用的程序架构就会回调这些方法来完成相关业务的处理。几乎所有基于回调的事件处理方法都有一个boolean类型的返回值,该返回值用于标识该处理方法是否能完全处理该事件:
1.如果处理事件的回调方法返回true,表明该处理方法已完全处理该事件,该事件不会传播出去。
2.如果事件处理的回调方法返回false,表明该处理方法并未完全处理事件,该事件会传播到外层组件去处理。
某组件上发生的事情不仅激发该组件上的回调方法,也会触发该组件所在Activity的回调方法——只要事件能传播到该Activity。例如,当某个组件上发生某个按键按下的事件时,Android系统最先触发的应该是该按键上绑定的监听器,接着才触发该组件个的事件回调方法,然后还会传播到该组件所在的Activity。当然前提是事件处理方法返回的是false。
参考:https://blog.csdn.net/csxwc/article/details/9351173
组件的回调方法:
boolean onKeyDown(int keyCode,KenyEvent enent) 当用户在该组件上按下某个键时触发的方法。
boolean onKeyLongPress(int keyCode,KeyEvent event) 当用户在该组件上长按某个按键时触发的方法。
boolean onKeyShortcut(int keyCode,KeyEvent event) 当一个键盘快捷键事件触发时 触发的方法。
boolean onKeyUp(int keyCode,KeyEvent event) 当用户在该组件上松开某个按键时触发的方法。
boolean onTouchEvent(MotionEvent event) 当用户子在该组件上触发触摸屏事件时 触发的方法。
boolean onTrackballEvent(MotionEvent event) 当用户在该组件上触发轨迹球屏事件时触发该方法。
3.Handler消息传递机制
为什么要使用Handler?
Android的APP中的用户点击某些事件源,有时候事件的反馈需要更新UI,而在Android中规定只能由主线程才能操作UI。那就不用子线程了,只让主线程更新UI不就完了吗?这样不行的,由于主线程可能会进行一些耗时的操作,比如文件读取,网络访问等,这些任务最好让子线程去操作,不然程序会出现卡顿。而让子线程去更新UI的话,如果多个子线程同时进行更新UI,UI的状态时不可知的,会有线程的安全问题,因此android规定只能由主线程更新UI。而Handler机制就是子线程对主线程发送消息用的,比如子线程通知主线程去更新一个界面的图片。
一说通信,那么一般会有两个部分,一个是信息的发送,一个是信息的接受处理。
在Handler的使用中,通常就是new一个Handler对象,然后重写它的handleMessage方法。
比如实现一个图片的定时更新:
public class MainActivity extends Activity {
//定义切换的图片的数组id
int imgids[] = new int[]{
R.drawable.s_1, R.drawable.s_2,R.drawable.s_3,
R.drawable.s_4,R.drawable.s_5,R.drawable.s_6,
R.drawable.s_7,R.drawable.s_8
};
int imgstart = 0;
final Handler myHandler = new Handler()
{
@Override
//重写handleMessage方法,根据msg中what的值判断是否执行后续操作
public void handleMessage(Message msg) {
if(msg.what == 0x123)
{
imgchange.setImageResource(imgids[imgstart++ % 8]);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ImageView imgchange = (ImageView) findViewById(R.id.imgchange);
//使用定时器,每隔200毫秒让handler发送一个空信息
new Timer().schedule(new TimerTask() {
@Override
public void run() {
myHandler.sendEmptyMessage(0x123);
}
}, 0,200);
//Timer().schedule(TimerTask task, Date when, long period);三个参数的意思分别是:
/*
1 ,task:所安排的时间线程
2,when:第一次执行的时间
3, period:间隔的执行时间
*/
}
}
4.TouchListener、OnTouchEvent、多点触碰
什么是触碰事件
触碰事件就是我们每对屏幕的一次操作(最常见就是点击,移动,和松开)都会产生触摸事件,每次产生的事件都会包含大量的信息(触摸点相对于当前View的x,y值,相对当前容器的x,y值,当前触摸的类型等)
这些信息都被封装在一个MotionEvent
类中,我们可以通过这个类提供的方法获取到。
基于监听的TouchListener
设置图片的TouchListener,示例:
imgtouch = (ImageView)findViewById(R.id.imgtouch);
imgtouch.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Toast.makeText(getApplicationContext(),"XXXX",Toast.LENGTH_LONG).show();
return true;
}
});
onTouch(View v, MotionEvent event)
:
这里面的参数依次是触发触摸事件的组件,触碰事件event
封装了触发事件的详细信息,同样包括事件的类型、触发时间等信息。比如event.getX()
,event.getY()
。
我们也可以对触摸的动作类型进行判断,使用event.getAction( )
再进行判断;如:
event.getAction == MotionEvent.ACTION_DOWN
:按下事件
event.getAction == MotionEvent.ACTION_MOVE
:移动事件
event.getAction == MotionEvent.ACTION_UP
:弹起事件
基于回调的onTouchEvent方法
首先我们需要明确的是,具有事件传递能力的是View
,ViewGroup
,Activity
。
事件传递规则:
ViewGroup接收到事件后进行事件的分派,如果自己需要处理这个事件,则进行拦截;如果不处理,则传递给子View进行处理,然后由子view进行分派,拦截和处理。可类比于:上级接到任务后进行任务分派,如果上级自己处理这个任务,则自己处理;如果不想处理,则把这个任务丢给下级进行处理…
参考:https://segmentfault.com/a/1190000004981942
那么对应的三个重要的方法:
1. dispatchTouchEvent
分发:
此方法用来处理事件的分发,当事件传递给当前view时,首先就是通过调用此方法来进行传递的。
如果当前view锁包含的子view的dispatchTouchEvent方法或者当前view的onTouchEvent处理了事件,通常返回true,表示事件已消费。如果没有处理则返回false。
2. onInterceptTouchEvent
拦截:
此方法用来判断某个事件是否需要拦截,如果某个view拦截了此事件,那么同一个时间序列中,此方法不会被再次调用。
3. onTouchEvent
处理:
用来处理事件,如果事件被消耗了,通常就返回true,
如果不做处理,则发挥false,并且在同一个时间序列中,当前view不会再接受到事件。
如果是ViewGroup容器,则以上三个方法都有,但是如果是View组件,只包含第一第三个,因为没有子组件只能由自己处理,不需要拦截。
参考:https://blog.csdn.net/qq475703980/article/details/92385368
事件传递如图:
图片来源于:https://segmentfault.com/a/1190000004981942,侵删
伪代码:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if(onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
来源于:https://blog.csdn.net/qq475703980/article/details/92385368
如果实现简单的触碰事件处理,那么此方式更多用于自定义View,只需要重写onTouchEvent方法,而这种触摸事件是基于回调的,如果我们返回的值是false的话,那么事件会继续向外传播,由外面的容器或者Activity进行处理。
例如get到触碰的位置坐标:
public class TouchView extends View{
public float X = 50;
public float Y = 50;
public TouchView(Context context,AttributeSet set)
{
super(context,set);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
this.X = event.getX();
this.Y = event.getY();
return true;
}
}
多点触碰
用的最多的就是图片的放大缩小了吧,一般设置2~4个点足够。
我们也可以对触摸的动作类型进行判断,这里有两个多点专用的操作:
MotionEvent.ACTION_POINTER_DOWN
:当屏幕上已经有一个点被按住,此时再按下其他点时触发。MotionEvent.ACTION_POINTER_UP
:当屏幕上有多个点被按住,松开其中一个点时触发(即非最后一个点被放开时)。
大概流程:
- 当我们一个手指触摸屏幕——触发
ACTION_DOWN
事件 - 接着有另一个手指也触摸屏幕——触发
ACTION_POINTER_DOWN
事件,如果还有其他手指触摸,继续触发 - 有一个手指离开屏幕——触发
ACTION_POINTER_UP
事件,继续有手指离开,继续触发 - 当最后一个手指离开屏幕——触发
ACTION_UP
事件 - 而且在整个过程中,
ACTION_MOVE
事件会一直不停地被触发
我们可以通过event.getX(int)
或者event.getY(int)
来获得不同触摸点的位置: 比如event.getX(0)
可以获得第一个接触点的X坐标,event.getX(1)
获得第二个接触点的X坐标这样… 另外,我们还可以在调用MotionEvent
对象的getPointerCount()
方法判断当前有多少个手指在触摸。
单指拖动图片,双指缩放图片示例:
private ImageView img_test;
// 縮放控制
private Matrix matrix = new Matrix();
private Matrix savedMatrix = new Matrix();
// 不同状态的表示:
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
// 定义第一个按下的点,两只接触点的重点,以及出事的两指按下的距离:
private PointF startPoint = new PointF();
private PointF midPoint = new PointF();
private float oriDis = 1f;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img_test = (ImageView) this.findViewById(R.id.img_test);
img_test.setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
ImageView view = (ImageView) v;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
// 单指
case MotionEvent.ACTION_DOWN:
matrix.set(view.getImageMatrix());
savedMatrix.set(matrix);
startPoint.set(event.getX(), event.getY());
mode = DRAG;
break;
// 双指
case MotionEvent.ACTION_POINTER_DOWN:
oriDis = distance(event);
if (oriDis > 10f) {
savedMatrix.set(matrix);
midPoint = middle(event);
mode = ZOOM;
}
break;
// 手指放开
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
// 单指滑动事件
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
// 是一个手指拖动
matrix.set(savedMatrix);
matrix.postTranslate(event.getX() - startPoint.x, event.getY() - startPoint.y);
} else if (mode == ZOOM) {
// 两个手指滑动
float newDist = distance(event);
if (newDist > 10f) {
matrix.set(savedMatrix);
float scale = newDist / oriDis;
matrix.postScale(scale, scale, midPoint.x, midPoint.y);
}
}
break;
}
// 设置ImageView的Matrix
view.setImageMatrix(matrix);
return true;
}
// 计算两个触摸点之间的距离
private float distance(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
// 计算两个触摸点的中点
private PointF middle(MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
return new PointF(x / 2, y / 2);
}
5.监听EditText的内容变化
监听EditText的内容变化
为EditText
设置监听器,不过这时不是OnClickListener
,而是TextWatcher
,我们可以调用EditText.addTextChangeListener(mTextWatcher)
为EditText
设置内容变化监听。
TextWatcher
类需要实现三个方法:
public void beforeTextChanged(CharSequence s, int start,int count, int after);
内容变化前public void onTextChanged(CharSequence s, int start, int before, int count);
内容变化中public void afterTextChanged(Editable s);
内容变化后
一般用的多的是最后一个方法,比如限制字数,限制输入内容等。
实现一个自定义EditText
,输入内容后右面出现一个×,点击可清空内容:
public class DelEditText extends EditText {
private Drawable imgClear;
private Context mContext;
public DelEditText(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}
private void init() {
imgClear = mContext.getResources().getDrawable(R.drawable.delete_gray);
addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable editable) {
setDrawable();
}
});
}
//绘制删除图片
private void setDrawable(){
if (length() < 1)
setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
else
setCompoundDrawablesWithIntrinsicBounds(null, null, imgClear, null);
}
//当触摸范围在右侧时,触发删除方法,隐藏叉叉
@Override
public boolean onTouchEvent(MotionEvent event) {
if(imgClear != null && event.getAction() == MotionEvent.ACTION_UP)
{
int eventX = (int) event.getRawX();
int eventY = (int) event.getRawY();
Rect rect = new Rect();
getGlobalVisibleRect(rect);
rect.left = rect.right - 100;
if (rect.contains(eventX, eventY))
setText("");
}
return super.onTouchEvent(event);
}
@Override
protected void finalize() throws Throwable {
super.finalize();
}
}
activity_main.xml
:
<demo.com.jay.buttondemo.DelEditText
android:id="@+id/edit_search"
android:layout_width="match_parent"
android:layout_height="32dp"
android:layout_margin="10dp"
android:background="@drawable/bg_frame_search"
android:hint="带删除按钮的EditText~"
android:maxLength="20"
android:padding="5dp"
android:singleLine="true" />
背景色
bg_frame_search.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@color/background_white" />
<corners android:radius="5dp" />
<stroke android:width="1px" android:color="@color/frame_search"/>
</shape>
颜色资源
color.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="reveal_color">#FFFFFF</color>
<color name="bottom_color">#3086E4</color>
<color name="bottom_bg">#40BAF8</color>
<color name="frame_search">#ADAEAD</color>
<color name="background_white">#FFFFFF</color>
<color name="back_red">#e75049</color>
</resources>
6.Gestures手势
Android提供手势检测,并为手势提供了相应的监听器。
Android中手势交互的执行顺序:
1. 手指触碰屏幕时,触发MotionEvent事件;
2. 该事件被OnTouchListener监听,可在它的onTouch()方法中获得该MotionEvent对象;
3. 通过GestureDetector转发MotionEvent对象给OnGestureListener;
4. 可以通过OnGestureListener获得该对象,然后获取相关信息,以及做相关处理。
注: MotionEvent: 这个类用于封装手势、触摸笔、轨迹球等等的动作事件。 其内部封装了两个重要的属性X和Y,这两个属性分别用于记录横轴和纵轴的坐标。 GestureDetector: 识别各种手势。 OnGestureListener: 这是一个手势交互的监听接口,其中提供了多个抽象方法, 并根据GestureDetector的手势识别结果调用相对应的方法。
GestureListener
监听手势的关键是:GestureListener
它给我们提供了下述的回调方法:
按下onDown
: 刚刚手指接触到触摸屏的那一刹那,就是触的那一下。
抛掷onFling
: 手指在触摸屏上迅速移动,并松开的动作。
长按onLongPress
: 手指按在持续一段时间,并且没有松开。
滚动onScroll
: 手指在触摸屏上滑动。
按住onShowPress
: 手指按在触摸屏上,它的时间范围在按下起效,在长按之前。
抬起onSingleTapUp
:手指离开触摸屏的那一刹那。
知道了GestureListener
的相关方法后,实现手势检测也很简单,步骤如下:
Step 1: 创建GestureDetector
对象,创建时需实现GestureListener
传入
Step 2: 将Activity或者特定组件上的TouchEvent
的事件交给GestureDetector
处理即可! 我们写个简单的代码来验证这个流程,即重写对应的方法:
public class MainActivity extends AppCompatActivity {
private MyGestureListener mgListener;
private GestureDetector mDetector;
private final static String TAG = "MyGesture";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//实例化GestureListener与GestureDetector对象
mgListener = new MyGestureListener();
mDetector = new GestureDetector(this, mgListener);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mDetector.onTouchEvent(event);
}
//自定义一个GestureListener,这个是View类下的,别写错哦!!!
private class MyGestureListener implements GestureDetector.OnGestureListener {
@Override
public boolean onDown(MotionEvent motionEvent) {
Log.d(TAG, "onDown:按下");
return false;
}
@Override
public void onShowPress(MotionEvent motionEvent) {
Log.d(TAG, "onShowPress:手指按下一段时间,不过还没到长按");
}
@Override
public boolean onSingleTapUp(MotionEvent motionEvent) {
Log.d(TAG, "onSingleTapUp:手指离开屏幕的一瞬间");
return false;
}
@Override
public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
Log.d(TAG, "onScroll:在触摸屏上滑动");
return false;
}
@Override
public void onLongPress(MotionEvent motionEvent) {
Log.d(TAG, "onLongPress:长按并且没有松开");
}
@Override
public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
Log.d(TAG, "onFling:迅速滑动,并松开");
return false;
}
}
}
但有时候我们不需要实现这么多的手势处理,比如只需要实现简单的滑动:官方提供了一个SimpleOnGestureListener
类,只需要将上面的OnGestureListener
替换SimpleOnGestureListener
。
比如下滑关闭Activity,上滑打开Activity:
public class MainActivity extends AppCompatActivity {
private GestureDetector mDetector;
private final static int MIN_MOVE = 200; //最小距离
private MyGestureListener mgListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//实例化SimpleOnGestureListener与GestureDetector对象
mgListener = new MyGestureListener();
mDetector = new GestureDetector(this, mgListener);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mDetector.onTouchEvent(event);
}
//自定义一个GestureListener,这个是View类下的,别写错哦!!!
private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float v, float v1) {
if(e1.getY() - e2.getY() > MIN_MOVE){
startActivity(new Intent(MainActivity.this, MainActivity.class));
Toast.makeText(MainActivity.this, "通过手势启动Activity", Toast.LENGTH_SHORT).show();
}else if(e1.getY() - e2.getY() < MIN_MOVE){
finish();
Toast.makeText(MainActivity.this,"通过手势关闭Activity",Toast.LENGTH_SHORT).show();
}
return true;
}
}
}
总体参考:https://www.runoob.com/w3cnote/android-tutorial-intro.html