Google Feed
Android 14为例
要想桌面带有Google feed负一屏,需要编译vendor/partner_gms/apps/SearchLauncher
SearchLauncher结构
1、libs/launcher_client.jar Google和feed apk通讯的工具
2、com/android/searchlauncher/OverlayCallbackImpl.java 初始化LauncherClient对象和实现LauncherClientCallbacks回调方法,并且在launcher各个生命周期通过LauncherClient同步给负一屏
3、负一屏的具体实现应该是被封装了,只能通过谷歌提供的接口,对负一屏进行操作
问题1:负一屏滚动一半的时候才会被唤出,缓慢滑动唤出负一屏需要在屏幕上滑动屏幕一半的距离,这在大屏上比较困难,不利于用户体验。
有关负一屏的操控方法都在一下这个类中实现(安卓14):
launcher3上的滑动的距离通过onScrollChang()这个接口传入负一屏,负一屏根据这个滑动距离来移动,原来是1:1的关系,一下代码修改为1:5的关系:
public class OverlayCallbackImpl
implements LauncherOverlay, LauncherClientCallbacks, LauncherOverlayManager,
OnSharedPreferenceChangeListener {
@Override
public void onScrollChange(float progress, boolean rtl) {
//modified by xu-24 at 2024/03/18 :When the large screen touch slides 0.1,
// the negative screen component needs to be updated to slide five times the large screen,
// which makes sliding the negative screen easier
//此处是我的代码修改点,传入的progress是在pagedview中获取到的滑动距离,原生代码中负一屏滚动的距离跟滑动距离是1:1的关系,故*5是的宽度较大的大屏也能轻松的滚动出负一屏
mClient.updateMove(progress*5);
}
}
一下是这个接口的部分调用堆栈信息:
03-18 04:38:28.985 13135 13135 I : 调用堆栈信息:
03-18 04:38:28.986 13135 13135 I : dalvik.system.VMStack.getThreadStackTrace(Native Method)
03-18 04:38:28.986 13135 13135 I : java.lang.Thread.getStackTrace(Thread.java:1841)
03-18 04:38:28.986 13135 13135 I : com.android.searchlauncher.OverlayCallbackImpl.onScrollChange(OverlayCallbackImpl.java:173)
03-18 04:38:28.986 13135 13135 I : com.android.launcher3.util.OverlayEdgeEffect.onPullDistance(OverlayEdgeEffect.java:52)
03-18 04:38:28.986 13135 13135 I : com.android.launcher3.PagedView.onTouchEvent(PagedView.java:1330)
03-18 04:38:28.987 13135 13135 I : com.android.launcher3.Workspace.onTouchEvent(Workspace.java:1095)
03-18 04:38:28.987 13135 13135 I : android.view.View.dispatchTouchEvent(View.java:15655)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3114)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2787)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
03-18 04:38:28.987 13135 13135 I : com.android.launcher3.views.BaseDragLayer.dispatchTouchEvent(BaseDragLayer.java:303)
03-18 04:38:28.987 13135 13135 I : com.android.launcher3.dragndrop.DragLayer.dispatchTouchEvent(DragLayer.java:225)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3142)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3120)
03-18 04:38:28.987 13135 13135 I : android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2801)
03-18 04:38:28.987 13135 13135 I : com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:490)
03-18 04:38:28.987 13135 13135 I : com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1904)
03-18 04:38:28.987 13135 13135 I : android.app.Activity.dispatchTouchEvent(Activity.java:4377)
03-18 04:38:28.987 13135 13135 I : com.android.launcher3.Launcher.dispatchTouchEvent(Launcher.java:2145)
03-18 04:38:28.987 13135 13135 I : com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:448)
03-18 04:38:28.987 13135 13135 I : android.view.View.dispatchPointerEvent(View.java:15919)
问题2:快速滑动的时候滑动速度很快才能进入负一屏,不友好用户使用体验
找到pagedview中的public boolean onTouchEvent(MotionEvent ev) 方法,我们只需要关注手指离开屏幕后的代码,找到shouldFlingForVelocity()这个方法的调用位置:
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final int activePointerId = mActivePointerId;
final int pointerIndex = ev.findPointerIndex(activePointerId);
if (pointerIndex == -1) return true;
final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev,
pointerIndex);
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocity = (int) mOrientationHandler.getPrimaryVelocity(velocityTracker,
mActivePointerId);
float delta = primaryDirection - mDownMotionPrimary;
View current = getPageAt(mCurrentPage);
if (current == null) {
Log.e(TAG, "current page was null. this should not happen.");
return true;
}
int pageOrientedSize = (int) (mOrientationHandler.getMeasuredSize(current)
* mOrientationHandler.getPrimaryScale(this));
boolean isSignificantMove = isSignificantMove(Math.abs(delta), pageOrientedSize);
mTotalMotion += Math.abs(mLastMotion - primaryDirection);
boolean passedSlop = mAllowEasyFling || mTotalMotion > mPageSlop;
boolean isFling = passedSlop && shouldFlingForVelocity(velocity);//shouldFlingForVelocity,此方法是根据速度判断滑动相关的方法,此处调用的是子类中的同名方法
boolean isDeltaLeft = mIsRtl ? delta > 0 : delta < 0;
boolean isVelocityLeft = mIsRtl ? velocity > 0 : velocity < 0;
if (DEBUG_FAILED_QUICKSWITCH && !isFling && mAllowEasyFling) {
Log.d("Quickswitch", "isFling=false vel=" + velocity
+ " threshold=" + mEasyFlingThresholdVelocity);
}
if (!mFreeScroll) {
// In the case that the page is moved far to one direction and then is flung
// in the opposite direction, we use a threshold to determine whether we should
// just return to the starting page, or if we should skip one further.
boolean returnToOriginalPage = false;
if (Math.abs(delta) > pageOrientedSize * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
Math.signum(velocity) != Math.signum(delta) && isFling) {
returnToOriginalPage = true;
}
int finalPage;
// We give flings precedence over large moves, which is why we short-circuit our
// test for a large move if a fling has been registered. That is, a large
// move to the left and fling to the right will register as a fling to the right.
if (((isSignificantMove && !isDeltaLeft && !isFling) ||
(isFling && !isVelocityLeft)) && mCurrentPage > 0) {
finalPage = returnToOriginalPage
? mCurrentPage : mCurrentPage - getPanelCount();
runOnPageScrollsInitialized(
() -> snapToPageWithVelocity(finalPage, velocity));
} else if (((isSignificantMove && isDeltaLeft && !isFling) ||
(isFling && isVelocityLeft)) &&
mCurrentPage < getChildCount() - 1) {
finalPage = returnToOriginalPage
? mCurrentPage : mCurrentPage + getPanelCount();
runOnPageScrollsInitialized(
() -> snapToPageWithVelocity(finalPage, velocity));
} else {
runOnPageScrollsInitialized(this::snapToDestination);
}
} else {
if (!mScroller.isFinished()) {
abortScrollerAnimation(true);
}
int initialScroll = mOrientationHandler.getPrimaryScroll(this);
int maxScroll = mMaxScroll;
int minScroll = mMinScroll;
if (((initialScroll >= maxScroll) && (isVelocityLeft || !isFling)) ||
((initialScroll <= minScroll) && (!isVelocityLeft || !isFling))) {
mScroller.springBack(initialScroll, 0, minScroll, maxScroll, 0, 0);
mNextPage = getDestinationPage();
} else {
int velocity1 = -velocity;
// Continue a scroll or fling in progress
mScroller.fling(initialScroll, 0, velocity1, 0, minScroll, maxScroll, 0, 0,
Math.round(getWidth() * 0.5f * OVERSCROLL_DAMP_FACTOR), 0);
int finalPos = mScroller.getFinalX();
mNextPage = getDestinationPage(finalPos);
runOnPageScrollsInitialized(this::onNotSnappingToPageInFreeScroll);
}
invalidate();
}
}
mEdgeGlowLeft.onRelease();
mEdgeGlowRight.onRelease();
// End any intermediate reordering states
resetTouchState();
break;
找到Workspace.java,中同名方法shouldFlingForVelocity的实现如下:
@Override
protected boolean shouldFlingForVelocity(int velocityX) {
//start add by fj 2024/03/14:
//If the negative screen has a scroll
// and the slide reaches the specified speed,
// and the slide is to the right, the negative screen is opened
//为解决这个问题,我选择在这个方法中添加我的修改代码,如下
if(velocityX >100 && Float.compare(Math.abs(mOverlayProgress), 0) > 0 && velocityX > 0){
mLauncher.openOverlay();//调用我在launcher.java中添加的方法打开负一屏
}
//end by fj
// When the overlay is moving, the fling or settle transition is controlled by the overlay.
//fj:此处return返回的结果,Float.compare(Math.abs(mOverlayProgress), 0) == 0是判断mOverlayProgress也就是负一屏滚动值,为0说明不是关于负一屏的滑动,原生代码在此处也对负一屏处的滑动做了判断处理
return Float.compare(Math.abs(mOverlayProgress), 0) == 0
&& super.shouldFlingForVelocity(velocityX);
}
ps:
launcher.java中的打开负一屏的方法实现:
/**
* Add by fj at 2024.03.13
* Open negative screen
*/
public void openOverlay(){
mOverlayManager.openOverlay(); //mOverlayManager是一个接口,其中所有的方法都在前文所述的OverlayCallbackImpl中被实现
}
问题三:Laucher3中在何处获取到的负一屏的滚动值呢?
Workspace.java中有着onOverlayScrollChanged实现:
/**
* The overlay scroll is being controlled locally, just update our overlay effect
*/
@Override
public void onOverlayScrollChanged(float scroll) {
mOverlayProgress = Utilities.boundToRange(scroll, 0, 1);
//mOverlayProgress即为负一屏的滚动值,在0到1的区间表示占屏幕宽度的比例
if (Float.compare(mOverlayProgress, 1f) == 0) {
if (!mOverlayShown) {
mOverlayShown = true;
mLauncher.onOverlayVisibilityChanged(true);
}
} else if (Float.compare(mOverlayProgress, 0f) == 0) {
if (mOverlayShown) {
mOverlayShown = false;
mLauncher.onOverlayVisibilityChanged(false);
}
}
int count = mOverlayCallbacks.size();
for (int i = 0; i < count; i++) {
mOverlayCallbacks.get(i).onOverlayScrollChanged(mOverlayProgress);
}
}