ViewPager嵌套ViewPager滑动冲突的解决
总结
* 1。点下的时候,要记录下手指按下的位置
* 2。滑动的时候,通过滑动的距离,判断是往哪个方向滑动
* 3。左滑的时候,到末尾不拦截,false;不到末尾拦截,true
* 右滑的时候,到开头不拦截,false;不到开头拦截,false
* 4. getParent().requestDisallowInterceptTouchEvent(disallowIntercept);的作用就是给
* 给ViewGroup设置一个标识位,不调用本省的onInterceptTouchEvent()方法
package com.joyy.android_project;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;
/**
* Time:2022/4/2 20:22
* Author:
* Description:
*/
public class RollViewPager extends ViewPager {
public RollViewPager(@NonNull Context context) {
super(context);
}
public RollViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
int downX = 0;
int downY = 0;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// y轴方向考虑移动整个模块,让其支持下拉刷新
// 在用系统的事件处理之前,先让自定义的viewpager满足我们定义的规则
// viewpager选中最后一个点的时候,由右向左滑动,需要让整个模块进行翻转
// viewpager选中第一个点的时候,由左向右滑动,需要让整个模块进行翻转
// 其余情况,翻转viewpager的图片
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = (int) ev.getX(); // 记录按下的位置
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) ev.getX();
int moveY = (int) ev.getY();
if (Math.abs(moveX - downX) < Math.abs(moveY - downY)) {
// y轴上的偏移量比x轴上的偏移量更多,可能会出发下拉刷新,需要响应事件的是大的模块
// 请求不拦截触摸事件(不是这样的,不拦截)
//让viewpager告知其父容器要拦截响应事件
requestParentIntercept(false);
} else {
// x轴偏移量大于y轴的偏移量的情况
if (moveX - downX < 0) { // 由右向左移动,最后一个点,翻转整个模块
if (getCurrentItem() == getAdapter().getCount() - 1) {
//让viewpager告知其父容器要拦截响应事件
requestParentIntercept(false);
} else if (getCurrentItem() < getAdapter().getCount() - 1) {
//让viewpager告知其父容器不要拦截响应事件
requestParentIntercept(true);
}
} else { // 由左向右移动,第一个点,翻转整个模块
if (getCurrentItem() == 0) {
//让viewpager告知其父容器要拦截响应事件
requestParentIntercept(false);
} else if (getCurrentItem() > 0) {
//让viewpager告知其父容器不要拦截响应事件
requestParentIntercept(true);
}
}
}
}
return super.dispatchTouchEvent(ev);
}
private void requestParentIntercept(boolean disallowIntercept) {
Log.i("RollViewPager", "disallowIntercept : " + disallowIntercept);
getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
}
/**
* 总结:
* 1。点下的时候,要记录下手指按下的位置
* 2。滑动的时候,通过滑动的距离,判断是往哪个方向滑动
* 3。左滑的时候,到末尾不拦截,false;不到末尾拦截,true
* 右滑的时候,到开头不拦截,false;不到开头拦截,false
* 4. getParent().requestDisallowInterceptTouchEvent(disallowIntercept);的作用就是给
* 给ViewGroup设置一个标识位,不调用本省的onInterceptTouchEvent()方法
*
* if (actionMasked == MotionEvent.ACTION_DOWN
* || mFirstTouchTarget != null) {
* // 这里判断子View是否允许父View被打断
* final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
* if (!disallowIntercept) {
* intercepted = onInterceptTouchEvent(ev);
* ev.setAction(action); // restore action in case it was changed
* } else {
* intercepted = false;
* }
* } else {
* // There are no touch targets and this action is not an initial down
* // so this view group continues to intercept touches.
* intercepted = true;
* }
*/
}
翻转Activity(Fragment)的生命周期
Activity的onSaveInstanceState()方法
Activity的onSaveInstanceState()方法Android onSaveInstanceState()和onRestoreInstanceState()调用时机
在手机内存不足时系统会回收掉不在栈顶位置的app
Activity的OnSaveInstanceState()方法,它只有具备以下条件的时候才会触发:
- 当按下HOME键的时候
- 长按HOME键,选择运行程序的时候
- 按下电源(关闭屏幕显示)时候
- 从Activity中启动其他Activity时候
- 屏幕方向切换时候(横竖屏切换)
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(TAG,"我正在被销毁,我保存了一些临时数据");
String tempData="Something you just typed";
outState.putString("data_key",tempData);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
Log.d(TAG,"onCreate----正在创建活动");
if (savedInstanceState!=null){
String tempDate=savedInstanceState.getString("data_key");
Log.d(TAG,tempDate);
}
}
onRestoreInstanceState什么时候被调用?
- 只有在Activity确实被系统回收,重新创建Activity的情况下才会被调用
- 被系统回收过的生命周期
onPause -> onSaveInstanceState -> onStop -> onDestroy -> onCreate -> onStart -> onRestoreInstanceState -> onResume - 没有被回收过的生命周期
onPause -> onSaveInstanceState -> onStop -> onRestart -> onStart -> onResume
onCreate()里也有Bundle参数,可以用来恢复数据,它和onRestoreInstanceState有什么区别?
3. 因为onSaveInstanceState不一定会调用,所以onCreate里的Bundle参数可能为空,如果在onCreate来恢复数据,一定要做非空判断
4. 而onRestoreInstanceState的Bundle参数一定不为空,因为它只有在上次activity被回收了才会调用。
而且onRestoreInstanceState是在onStart之后被调用的。
onRestoreInstanceState可以不调用super,但onCreate必须调用super
onSaveInstanceState都保留了什么参数
Activity 状态保存 OnSaveInstanceState参数解析
Android 学习笔记之实时保存数据-现场保护onSaveInstanceState()
Android常识面试题,onSaveInstanceState
Activity 状态保存 OnSaveInstanceState参数解析
【Android 应用开发】Activity 状态保存 OnSaveInstanceState参数解析
Fragment 生命周期和使用
/**
* Called to retrieve per-instance state from an activity before being killed
* so that the state can be restored in {@link #onCreate} or
* {@link #onRestoreInstanceState} (the {@link Bundle} populated by this method
* will be passed to both).
*
* <p>This method is called before an activity may be killed so that when it
* comes back some time in the future it can restore its state. For example,
* if activity B is launched in front of activity A, and at some point activity
* A is killed to reclaim resources, activity A will have a chance to save the
* current state of its user interface via this method so that when the user
* returns to activity A, the state of the user interface can be restored
* via {@link #onCreate} or {@link #onRestoreInstanceState}.
*
* <p>Do not confuse this method with activity lifecycle callbacks such as {@link #onPause},
* which is always called when the user no longer actively interacts with an activity, or
* {@link #onStop} which is called when activity becomes invisible. One example of when
* {@link #onPause} and {@link #onStop} is called and not this method is when a user navigates
* back from activity B to activity A: there is no need to call {@link #onSaveInstanceState}
* on B because that particular instance will never be restored,
* so the system avoids calling it. An example when {@link #onPause} is called and
* not {@link #onSaveInstanceState} is when activity B is launched in front of activity A:
* the system may avoid calling {@link #onSaveInstanceState} on activity A if it isn't
* killed during the lifetime of B since the state of the user interface of
* A will stay intact.
*
* <p>The default implementation takes care of most of the UI per-instance
* state for you by calling {@link android.view.View#onSaveInstanceState()} on each
* view in the hierarchy that has an id, and by saving the id of the currently
* focused view (all of which is restored by the default implementation of
* {@link #onRestoreInstanceState}). If you override this method to save additional
* information not captured by each individual view, you will likely want to
* call through to the default implementation, otherwise be prepared to save
* all of the state of each view yourself.
*
* <p>If called, this method will occur after {@link #onStop} for applications
* targeting platforms starting with {@link android.os.Build.VERSION_CODES#P}.
* For applications targeting earlier platform versions this method will occur
* before {@link #onStop} and there are no guarantees about whether it will
* occur before or after {@link #onPause}.
*
* @param outState Bundle in which to place your saved state.
*
* @see #onCreate
* @see #onRestoreInstanceState
* @see #onPause
*/
protected void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mAutoFillResetNeeded) {
outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
getAutofillManager().onSaveInstanceState(outState);
}
dispatchActivitySaveInstanceState(outState);
}
自定义ViewPager
总结:
- 通过addView添加子View
- 通过onMeasure测量子View的宽高
- 通过layout布局子View的位置
- 通过onTouchEvent来拦截事件
4.1 ACTION_DOWN的时候记录按下的位置
4.2 ACTION_MOVE的时候,判断方向,并通过scrollTo来移动位置
4.3 ACTION_UP的时候,计算要回落的点,invalidate会调用computeScroll方法,通过Scroller移动到相应的位置
package com.joyy.android_project;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
/**
* Time:2022/4/3 17:02
* Author:
* Description:
*/
public class MyViewPager extends ViewGroup {
private void log(String msg) {
Log.i("MyViewPager", msg);
}
private GestureDetector mGestureDetector;
private int scrollX;
private int position;
private Scroller mScroller;
private int originalX = 0;
private int originalY = 0;
public MyViewPager(Context context) {
super(context);
initView(context);
}
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
// public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
// super(context, attrs, defStyleAttr, defStyleRes);
// initView(context);
// }
private void initView(Context context) {
mScroller = new Scroller(context);
mGestureDetector = new GestureDetector(
context,
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// 当有手指在屏幕上滑动的时候回调
// distanceX为正时候,向左移动,为负时,向右移动
// 移动屏幕的方法scrollBy,很重要,这个方法会调用onScrollChanged方法,并刷新视图
// dx表示x方向上移动的距离,dy表示y方向上移动的距离。
scrollBy((int) distanceX, 0);
return super.onScroll(e1, e2, distanceX, distanceY);
}
}
);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 如果左右滑动,就需要拦截,上下滑动,不需要拦截
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
originalX = (int) ev.getX();
originalY = (int) ev.getY();
//这个时候还需要把将ACTION_DOWN传递给手势识别器,因为拦截了MOVE的事件后,DOWN的事件还是要给手势识别器处理,否则会丢失事件,滑动的时候会存在bug。
mGestureDetector.onTouchEvent(ev);
break;
case MotionEvent.ACTION_MOVE:
// 上下滑动拦截,左右滑动不拦截
int currentX = (int) ev.getX();
int currentY = (int) ev.getY();
int dx = currentX - originalX;
int dy = currentY - originalY;
if (Math.abs(dx) > Math.abs(dy)) {
// 左右滑动
return true; // 中断事件传递,不允许孩子响应事件
} else {
return false;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 将触摸事件传递手势识别器
mGestureDetector.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
scrollX = getScrollX(); // 相对于初始位置滑动的距离
// 你滑动的距离加上屏幕的一半,除以屏幕宽度,就是当前图片显示的position,
// 如果你滑动距离超过了屏幕的一半,这个position就+1
position = (getScrollX() + getWidth() / 2) / getWidth();
// 滑动到最后一张的时候,不能出边界
if (position >= getChildCount()) {
position = getChildCount() - 1;
}
if (position < 0) {
position = 0;
}
break;
case MotionEvent.ACTION_UP:
// 绝对滑动,直接滑到指定的x,y的位置,较迟钝
// scrollTo(position*getWidth(), 0);
// 滚动,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量
mScroller.startScroll(scrollX, 0, -(scrollX - position * getWidth()), 0);
// 使用invalidate这个方法会执行一个回调方法computeScroll, 我们来重写这个方法
invalidate();
break;
}
//return super.onTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
//super.computeScroll();
log("computeScroll");
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), 0);
postInvalidate();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
log("onMeasure");
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
log("onLayout");
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
view.layout(i * getWidth(), 0, (i + 1) * getWidth(), getHeight());
}
}
}
requestLayout, invalidate, postInvalidate
requestLayout()的执行流程
强引用、软引用、弱引用(实例)
Java基础篇 - 强引用、弱引用、软引用和虚引用
强引用:
- 强引用是使用最普遍的引用。如果有强引用,那垃圾回收器绝不会回收它。
- 当内存空间不足时,Java虚拟机宁愿抛出OutOfMemeoryError,也不会回收。
软引用:
- 如果一个对象是软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足时,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
- 软引用可用来实现内存敏感的高速缓存。
- 软引用可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用的对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。
- 注意:软引用对象在jvm内存不够的时候才会被回收,我们调用System.gc()方法只是起通知作用,JVM什么时候扫描回收对象是JVM自己的状态决定。就算扫描到软应用对象也不一定会回收它,只有内存不够时才会被回收。
- 垃圾回收线程就在虚拟机抛出OutOfMemeoryError之前回收软引用对象,而且虚拟机会尽可能优先回收长时间闲置不用的软引用对象。对那些刚构建的或刚使用过的“较新的”软对象会被虚拟机尽可能保留,这就是引入引用队列ReferenceQueue的原因。
弱引用:
- 弱引用和软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快的发现那些只具有弱引用的对象。
- 注意:如果一个对象是偶尔(很少)的使用,并且希望在使用时候随时获取到,但由不想影响此对象的垃圾收集,那么你应该用WeakReference来记住此对象。
引用类型 | 被垃圾回收时间 | 用途 | 生存时间 |
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 当内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 正常垃圾回收时 | 对象缓存 | 垃圾回收后终止 |
虚引用 | 正常垃圾回收时 | 跟踪对象的垃圾回收 | 垃圾回收后终止 |
软引用
public class SoftDemo {
public static void main(String[] args) {
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
String str = new String("abc");
SoftReference<String>softReference = new SoftReference<>(str, referenceQueue);
str = null;
System.gc();
System.out.println(softReference.get());
Reference reference = referenceQueue.poll();
System.out.println(reference);
}
}
弱引用
package com.flannery;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.LinkedList;
// javascript:void(0)
public class WeakDemo {
// 弱引用队列
private final static ReferenceQueue<GCTarget> REFERENCE_QUEUE
= new ReferenceQueue<>();
public static void main(String[] args) {
LinkedList<GCTargetWeakReference> gcTargetList = new LinkedList<>();
// 创建弱引用的对象,一次加入链表中
for (int i = 0; i < 5; i++) {
GCTarget gcTarget = new GCTarget(String.valueOf(i));
GCTargetWeakReference weakReference = new GCTargetWeakReference(gcTarget, REFERENCE_QUEUE);
gcTargetList.add(weakReference);
System.out.println("Just created GCTargetWeakReference obj: " +
gcTargetList.getLast());
}
// 通知gc进行回收
System.gc();
try {
// 休息几分钟,等待上面的垃圾回收线程运行完成
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 检查关联的引用队列是否为空
Reference<? extends GCTarget> reference;
while ((reference = REFERENCE_QUEUE.poll()) != null) {
if (reference instanceof GCTargetWeakReference) {
System.out.println("In queue, id is: " + ((GCTargetWeakReference) (reference)).id);
}
}
}
}
class GCTarget {
// 对象的ID
public String id;
//占用内存空间
byte[] buffer = new byte[1024];
public GCTarget(String id) {
this.id = id;
}
@Override
protected void finalize() throws Throwable {
//super.finalize();
System.out.println("Finalizing GCTarget, id is : " + id);
}
}
class GCTargetWeakReference extends WeakReference<GCTarget> {
String id;
public GCTargetWeakReference(GCTarget referent) {
super(referent);
this.id = referent.id;
}
public GCTargetWeakReference(GCTarget referent, ReferenceQueue<? super GCTarget> q) {
super(referent, q);
this.id = referent.id;
}
}
Activity启动模式
Activity的四种启动模式Activity的四种启动模式
Activity的四种启动模式详解
四种启动模式:
- standard模式
- 标准模式
- 特点:每一次都糊创建一个新的Activity, 这哥新的Activity总放在栈顶;
- singleTop模式
每当需要启动Activity时,系统首先检查栈顶的Activity是否存在一样的Activity,如果存在,则直接使用栈顶已经存在的Activity,否则新建一个Activity。 - singleTask模式
每当启动一个Activity时,系统会检查栈中是否存在该Activity的实例,如果存在,否则该实例的newInstance()方法重用该Activity,并把上面的Activity销毁,使其处于激活状态–栈顶,否则重新创建一个新的实例 - singleInstance模式
每当需要启动一个Activity时,系统会检查栈中是否存在一样的Activity实例,如果存在,则会调用newIntent()给它开一个单间,即重新开一个线程存放,这种模式只会创建一次,即会调用一次onCreate()方法,除非Activity被销毁。
比较耗资源,并且使用该模式会存在bug–startActivityForResult时,会报错
查看栈
dumpsys activity activities | grep “ActivityRecord”
- standart
- singleTop
- singleTask
- singleInstnce
Activity的Flags
- FLAG_ACTIVITY_NEW_TASK
- FLAG_ACTIVITY_SINGLE_TOP
- FLAG_ACTIVITY_CLEAN_TOP
- FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
启动模式的实际应用场景
- SingleTask模式的运用场景
最常见的应用场景就是保持我们应用开启后仅仅有一个Activity的实例。最典型的样例就是应用中展示的主页(Home页)。
假设用户在主页跳转到其他页面,运行多次操作后想返回到主页,假设不使用SingleTask模式,在点击返回的过程中会多次看到主页,这明显就是设计不合理了。 - SingleTop模式的运用场景
假设你在当前的Activity中又要启动同类型的Activity,此时建议将此类型Activity的启动模式指定为SingleTop,能够降低Activity的创建,节省内存!
LauchMode | Instance |
standard | 邮件、mainfest中没有配置就默认标准模式 |
singleTop | 登录页面、WXPayEntryActivity、WXEntryActivity 、推送通知栏 |
singleTask | 程序模块逻辑入口:主页面(Fragment的containerActivity)、WebView页面、扫一扫页面、电商中:购物界面,确认订单界面,付款界面 |
singleInstance | 系统Launcher、锁屏键、来电显示等系统应用 |
事件分发
Handler使用引起的内存泄漏原因及解决办法
Handler使用引起的内存泄漏原因以及解决办法都 2021 年了,还有人在研究 Handler?面试问Handler内存泄露的场景,别就只知道静态内部类&弱引用!
发生内存泄漏的原因
- 当一个android应用程序启动的时候,frameworks会自动为这个应用程序在主线程创建一个Looper对象。这个被创建的Looper对象也有它主要的工作,它主要的工作就是不断地处理消息队列中的消息对象。在android应用程序中,所有主要的框架事件(例如Activity的生命周期方法,按钮的点击事件等等)都包含在消息对象里面,然后被添加到Looper要处理的消息队列中,主线程的Looper一直存在于整个应用程序的生命周期中。
- 当一个Handler在主线程中被初始化。那它就一直都和Looper的消息队列相关联着。当消息被发送到Looper关联的消息队列的时候,会持有一个Handler的引用,以便于当Looper处理消息的时候,框架可以调用Handler的handleMessage(Message msg)。
- 在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用。静态内部类则不会持有外部类的引用。
ThreadPoolExcutors什么时候不会使用到非线程池
使用ThreadPoolExecutor遇到的核心线程被阻塞,非核心线程未按照预期运行问题
项目表述方面
先有架构,要讲故事