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 生命周期和使用
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;
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);
}
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) {
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();
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 = (getScrollX() + getWidth() / 2) / getWidth();
if (position >= getChildCount()) {
position = getChildCount() - 1;
}
if (position < 0) {
position = 0;
}
break;
case MotionEvent.ACTION_UP:
mScroller.startScroll(scrollX, 0, -(scrollX - position * getWidth()), 0);
invalidate();
break;
}
return true;
}
@Override
public void 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;
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());
}
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 {
public String id;
byte[] buffer = new byte[1024];
public GCTarget(String id) {
this.id = id;
}
@Override
protected void finalize() throws Throwable {
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遇到的核心线程被阻塞,非核心线程未按照预期运行问题
项目表述方面
先有架构,要讲故事
|