Launcher3--抽屉

抽屉是用来放置安卓手机中所有需要显示到Launcher上的(当然也可以进行过滤,将不想显示的隐藏起来)应用和小部件,启动应用、添加快捷方式到桌面、卸载等。之前也提到过,有些Launcher是没有抽屉的,如MIUI的Launcher。在Launcher3中,默认是有的,当然,也提供了不显示抽屉的方法,这个后面会说到,在此先了解下抽屉。

  

一、布局

抽屉的布局文件是apps_customize_pane.xml,被include在launcher.xml中,

launcher.xml

        <include layout="@layout/apps_customize_pane"
            android:id="@+id/apps_customize_pane"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="invisible" />

apps_customize_pane.xml

<com.android.launcher3.AppsCustomizeTabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto"
    android:clipChildren="false">

    <LinearLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:orientation="vertical">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:clipChildren="false">
            <FrameLayout
                android:id="@+id/fake_page_container"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:clipChildren="false"
                android:clipToPadding="false">
                <FrameLayout
                    android:id="@+id/fake_page"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:visibility="invisible"
                    android:clipToPadding="false" />
            </FrameLayout>
            <com.android.launcher3.AppsCustomizePagedView
                android:id="@+id/apps_customize_pane_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                launcher:widgetCountX="@integer/apps_customize_widget_cell_count_x"
                launcher:widgetCountY="@integer/apps_customize_widget_cell_count_y"
                launcher:maxGap="@dimen/workspace_max_gap"
                launcher:pageIndicator="@+id/apps_customize_page_indicator" />
        </FrameLayout>
        <include
            android:id="@+id/apps_customize_page_indicator"
            layout="@layout/page_indicator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    </LinearLayout>
</com.android.launcher3.AppsCustomizeTabHost>

这个就是抽屉的树形结构,AppsCustomizeTabHost是根视图,id/content是内容区域,包含一个FrameLayout和页面指示器indicator,这个FrameLayout也包含两块,上面一块是用作过渡页面,下面是AppsCustomizePagedView,就是用来显示app列表或小部件的,是最核心的部分。

二、数据加载和显示

首先需要弄清楚的是,并不是我们点击了抽屉按钮进入抽屉页才开始加载数据的,我们之前分析了<<Launcher3的加载流程>>,知道这些数据在Launcher启动过程中就加载了。这个也很好理解,Android系统中安装了很多应用,如果每次打开抽屉都要加载数据,那可想而知是多么糟糕的体验。

这部分的数据加载就是在<<Launcher3的加载流程>>中分析的loadAndBindAllApps过程,此过程已将应用数据保存到数据库中,并且设置到AppsCustomizePagedView中,详细过程就不在介绍了。很明显,这个时候要做的就是将其显示,并将Workspace隐藏。

进入抽屉的途径一个是点击桌面抽屉按钮图标,另一个是长按桌面选择小部件按钮,这两个操作其实进入的是同一个界面,只不过是根据操作的不同选择加载应用还是小部件,那我们就以显示应用列表来分析。

    public void onClick(View v) {
        .............
        } else if (v == mAllAppsButton) {// 抽屉按钮
            onClickAllAppsButton(v);
        } else if (tag instanceof AppInfo) {// 应用列表中的应用
         ............
    }
    protected void onClickAllAppsButton(View v) {
        if (LOGD) Log.d(TAG, "onClickAllAppsButton");
        // copy db
        CommonUtil.copyDBToSDcard();
        // end
        if (isAllAppsVisible()) {// 抽屉页面是否可见,实际情况在抽屉页时,不会显示按钮
            showWorkspace(true);
        } else {
            showAllApps(true, AppsCustomizePagedView.ContentType.Applications, false);
        }
        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.onClickAllAppsButton(v);
        }
    }

这里根据抽屉页是否可见来确定是显示Workspace还是抽屉,但在实际情况中抽屉中不会显示抽屉按钮,所以也就不可能执行到showWorkspace这个方法中。直接看showAllApps方法,

    void showAllApps(boolean animated, AppsCustomizePagedView.ContentType contentType,
                     boolean resetPageToZero) {
        if (mState != State.WORKSPACE) return;

        if (resetPageToZero) {// 是否需要恢复到首页
            mAppsCustomizeTabHost.reset();
        }
        showAppsCustomizeHelper(animated, false, contentType);
        mAppsCustomizeTabHost.post(new Runnable() {
            @Override
            public void run() {
                // We post this in-case the all apps view isn't yet constructed.
                mAppsCustomizeTabHost.requestFocus();// 给抽屉界面焦点
            }
        });

        // Change the state *after* we've called all the transition code
        mState = State.APPS_CUSTOMIZE;// 更新页面状态未APPS_CUSTOMIZE

        // Pause the auto-advance of widgets until we are out of AllApps
        mUserPresent = false;
        updateRunning();
        closeFolder();// 关闭文件夹

        // Send an accessibility event to announce the context change
        getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    }

这里面调用了showAppsCustomizeHelper方法,这是显示抽屉的的一个帮助方法,与此方法对应的是hideAppsCustomizeHelper方法,很显然使用隐藏抽屉时调用的,这两个方法实现很相似,我们这里只分析showAppsCustomizeHelper。

        if (mStateAnimation != null) {// 重置mStateAnimation
            mStateAnimation.setDuration(0);
            mStateAnimation.cancel();
            mStateAnimation = null;
        }

重置AnimatorSet,其实这个方法里面最主要就是实现各种动画效果,Workspace上的动画、抽屉上的动画。

        boolean material = Utilities.isLmpOrAbove();// sdk版本是否大于等于21

        final Resources res = getResources();

        // 定义了一些动画时长
        final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
        final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
        final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime);
        final int itemsAlphaStagger = res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);

        final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);//缩放大小
        // 从Workspace切换到AppsCustomizeTabHost
        final View fromView = mWorkspace;
        final AppsCustomizeTabHost toView = mAppsCustomizeTabHost;

        final ArrayList<View> layerViews = new ArrayList<View>();// DragLayer上的View列表

定义了一些变量,material来判断sdk版本,后面会根据这个布尔变量来进行不同的动画设置,在Android
L及以上采用了material design,所有在较高的版本上可以有一些更好的动画效果。然后还定义动画时长,缩放比例等。

        Workspace.State workspaceState = contentType == AppsCustomizePagedView.ContentType.Widgets ?
                Workspace.State.OVERVIEW_HIDDEN : Workspace.State.NORMAL_HIDDEN;
        Animator workspaceAnim = mWorkspace.getChangeStateAnimation(workspaceState, animated, layerViews);// 定义切换时Workspace上的动画
        // 设置加载的数据类型
        if (!LauncherAppState.isDisableAllApps() || contentType == AppsCustomizePagedView.ContentType.Widgets) {
            // Set the content type for the all apps/widgets space
            mAppsCustomizeTabHost.setContentTypeImmediate(contentType);
        }

设置加载内容的类型,有两种类型:application和widget,这里是application类型。

        // If for some reason our views aren't initialized, don't animate
        boolean initialized = getAllAppsButton() != null;// 是否初始化完成
        animated && initialized

来判断是否实现动画效果,我们直接看动画是怎么实现的。

            mStateAnimation = LauncherAnimUtils.createAnimatorSet();// 创建AnimatorSet
            final AppsCustomizePagedView content = (AppsCustomizePagedView)
                    toView.findViewById(R.id.apps_customize_pane_content);// 抽屉内容组件

            final View page = content.getPageAt(content.getCurrentPage());// 抽屉当前页
            final View revealView = toView.findViewById(R.id.fake_page);// 一个过渡页面,用来实现动画

            final boolean isWidgetTray = contentType == AppsCustomizePagedView.ContentType.Widgets;
            // 设置过渡页面的背景,根据类型分别设置
            if (isWidgetTray) {
                revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
            } else {
                revealView.setBackground(res.getDrawable(R.drawable.quantum_panel));
            }

初始化抽屉页面的组件,其中revealView 是一个过渡页,用来实现动画效果的,动画结束后将其隐藏。

            // 先隐藏真实页面,显示过渡页面
            // Hide the real page background, and swap in the fake one
            content.setPageBackgroundsVisible(false);
            revealView.setVisibility(View.VISIBLE);
            // We need to hide this view as the animation start will be posted.
            // alpha置为0
            revealView.setAlpha(0);

            int width = revealView.getMeasuredWidth();
            int height = revealView.getMeasuredHeight();
            float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4);

            // 偏移量置为0
            revealView.setTranslationY(0);
            revealView.setTranslationX(0);

            // Get the y delta between the center of the page and the center of the all apps button
            int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
                    getAllAppsButton(), null);

            float alpha = 0;
            float xDrift = 0;
            float yDrift = 0;
            if (material) {// sdk > 21 ?
                alpha = isWidgetTray ? 0.3f : 1f;
                yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1];
                xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0];
            } else {
                yDrift = 2 * height / 3;
                xDrift = 0;
            }
            final float initAlpha = alpha;

动画设置之前的一些初始化工作,将过渡页面的透明度、偏移量都先置0,然后设置动画时的透明度初始值和偏移量的初始值。

            revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
            layerViews.add(revealView);
            PropertyValuesHolder panelAlpha = PropertyValuesHolder.ofFloat("alpha", initAlpha, 1f);
            PropertyValuesHolder panelDriftY = PropertyValuesHolder.ofFloat("translationY", yDrift, 0);
            PropertyValuesHolder panelDriftX = PropertyValuesHolder.ofFloat("translationX", xDrift, 0);

            ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
                    panelAlpha, panelDriftY, panelDriftX);

            panelAlphaAndDrift.setDuration(revealDuration);
            panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));

            mStateAnimation.play(panelAlphaAndDrift);

定义了动画的类型、时长和变化速率等。这是一个组合动画,很明显动画效果是透明度的变化和偏移量的变化。

            // 抽屉当前页的动画
            if (page != null) {
                page.setVisibility(View.VISIBLE);
                page.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                layerViews.add(page);

                ObjectAnimator pageDrift = ObjectAnimator.ofFloat(page, "translationY", yDrift, 0);
                page.setTranslationY(yDrift);
                pageDrift.setDuration(revealDuration);
                pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
                pageDrift.setStartDelay(itemsAlphaStagger);
                mStateAnimation.play(pageDrift);

                page.setAlpha(0f);
                ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(page, "alpha", 0f, 1f);
                itemsAlpha.setDuration(revealDuration);
                itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
                itemsAlpha.setStartDelay(itemsAlphaStagger);
                mStateAnimation.play(itemsAlpha);
            }

这一段是抽屉当前页的动画效果,也是用属性动画来实现的,关于属性动画的使用可参考<<属性动画之ObjectAnimator>>。

然后是页面指示器和sdk>21的动画,这个就不再细说了,到动画监听,

            mStateAnimation.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
                    dispatchOnLauncherTransitionEnd(toView, animated, false);

                    // 隐藏过渡页面
                    revealView.setVisibility(View.INVISIBLE);
                    revealView.setLayerType(View.LAYER_TYPE_NONE, null);
                    if (page != null) {
                        page.setLayerType(View.LAYER_TYPE_NONE, null);
                    }
                    // 显示抽屉
                    content.setPageBackgroundsVisible(true);

                    // Hide the search bar
                    // 隐藏搜索栏
                    if (mSearchDropTargetBar != null) {
                        mSearchDropTargetBar.hideSearchBar(false);
                    }

                    // This can hold unnecessary references to views.
                    mStateAnimation = null;
                }

            });

动画结束后:隐藏过渡页面;显示抽屉内容;隐藏搜索栏。

            // Workspace动画效果
            if (workspaceAnim != null) {
                mStateAnimation.play(workspaceAnim);
            }

这个是Workspace的动画,该动画定义在Workspace.java的getChangeStateAnimation方法中,该方法定义了多种情况下的动画效果,如Workspace到桌面缩略图、桌面缩略图到Workspace、Workspace到抽屉等等,进行alpha、scale等设置。

最后定义一个runnable执行块,用于动画播放,

            final Runnable startAnimRunnable = new Runnable() {
                public void run() {
                    // Check that mStateAnimation hasn't changed while
                    // we waited for a layout/draw pass
                    if (mStateAnimation != stateAnimation)
                        return;
                    dispatchOnLauncherTransitionStart(fromView, animated, false);
                    dispatchOnLauncherTransitionStart(toView, animated, false);

                    revealView.setAlpha(initAlpha);
                    if (Utilities.isLmpOrAbove()) {// sdk > 21 ?
                        for (int i = 0; i < layerViews.size(); i++) {
                            View v = layerViews.get(i);
                            if (v != null) {
                                if (Utilities.isViewAttachedToWindow(v)) v.buildLayer();
                            }
                        }
                    }
                    mStateAnimation.start();// 执行动画
                }
            };

这样动画结束后,抽屉就显示出来,该隐藏的也隐藏了。如果是没有动画的情况,直接设为可见就行了,但会显得比较突兀,体验差了点。

另外,在该方法中,多次调用了dispatchOnLauncherTransitionXXX方法,最终调用View中实现了LauncherTransitionable页面过渡接口的方法,在切换的不同阶段做相应的处理。

interface LauncherTransitionable {
    View getContent();
    void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
    void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
    void onLauncherTransitionStep(Launcher l, float t);
    void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
}

三、自定义修改

1、如何更换抽屉背景?

Launcher3中,抽屉内容的背景默认是白色的,如果想改成透明的,该怎么修改?

一般情况下,我们首先想到的是在布局文件中找到AppsCustomizePagedView的布,然后将背景设为透明的,

            <com.android.launcher3.AppsCustomizePagedView
                android:id="@+id/apps_customize_pane_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                launcher:widgetCountX="@integer/apps_customize_widget_cell_count_x"
                launcher:widgetCountY="@integer/apps_customize_widget_cell_count_y"
                launcher:maxGap="@dimen/workspace_max_gap"
                launcher:pageIndicator="@+id/apps_customize_page_indicator" />

这个方法显然是不能实现的,因为在AppsCustomizePagedView中还有一层AppsCustomizeCellLayout,一个列表页就是一个AppsCustomizeCellLayout,在<<Launcher3的加载流程>>中,有提到过对每一页的设置,直接找出这部分代码,

launcher3\src\main\java\com\android\launcher3\AppsCustomizePagedView.java

    // 设置page的表格、背景色
    private void setupPage(AppsCustomizeCellLayout layout) {
        layout.setGridSize(mCellCountX, mCellCountY);// 设置页面表格数

        // Note: We force a measure here to get around the fact that when we do layout calculations
        // immediately after syncing, we don't have a proper width.  That said, we already know the
        // expected page width, so we can actually optimize by hiding all the TextView-based
        // children that are expensive to measure, and let that happen naturally later.
        setVisibilityOnChildren(layout, View.GONE);
        int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
        int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
        layout.measure(widthSpec, heightSpec);

        // 设置page背景色
        Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel);
        if (bg != null) {
            bg.setAlpha(mPageBackgroundsVisible ? 255: 0);
            layout.setBackground(bg);
        }

        setVisibilityOnChildren(layout, View.VISIBLE);
    }

这里设置了AppsCustomizeCellLayout的背景色,我们将其设置透明背景,看能否达到效果。

        // 设置page背景色
//        Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel);
//        if (bg != null) {
//            bg.setAlpha(mPageBackgroundsVisible ? 255: 0);
//            layout.setBackground(bg);
//        }
        layout.setBackgroundColor(Color.TRANSPARENT);

这样就满足了效果,但是文字是灰色的有些不协调,我们改成白色的,这个在syncAppsPageItems方法中,做如下修改,

        for (int i = startIndex; i < endIndex; ++i) {// 循环添加items
            AppInfo info = mApps.get(i);
            BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(R.layout.apps_customize_application, layout, false);
            icon.applyFromApplicationInfo(info);
            icon.setOnClickListener(mLauncher);
            icon.setOnLongClickListener(this);
            icon.setOnTouchListener(this);
            icon.setOnKeyListener(this);
            icon.setOnFocusChangeListener(layout.mFocusHandlerView);
            icon.setTextColor(Color.WHITE); // modify text color

            .................................
        }

2、如何改变行和列数?

可能已经注意到了,在布局文件中通过launcher:widgetCountX,launcher:widgetCountY来设置小部件没有显示数量,之所以可以这么设置,是因为在AppsCustomizePagedView中定义了这两个属性。

        // Save the default widget preview background
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
        mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
        mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
    但对于application而言,并没有定义类似的属性,那如何来改变行列数呢?首先得知道行和列是怎么得到的。mCellCountX和mCellCountY这两个变量分别代表行数和列数,它们的值是怎么得到的呢?
    protected void onDataReady(int width, int height) {
        // Now that the data is ready, we can calculate the content width, the number of cells to
        // use for each page
        LauncherAppState app = LauncherAppState.getInstance();
        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
        mCellCountX = (int) grid.allAppsNumCols;
        mCellCountY = (int) grid.allAppsNumRows;
        .....................................
    }
    跟allAppsNumCols和allAppsNumRows相关,这两个值在DeviceProfile.java中定义的,
    private void updateIconSize(float scale, int drawablePadding, Resources resources,
                                DisplayMetrics dm) {
        ...................
        // All Apps
        allAppsCellWidthPx = allAppsIconSizePx;
        allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
        int maxLongEdgeCellCount =
                resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
        int maxShortEdgeCellCount =
                resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
        int minEdgeCellCount =
                resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
        int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
        int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);

        if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) {
            allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount;
            allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount;
        } else {
            allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
                    (allAppsCellHeightPx + allAppsCellPaddingPx);
            allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
            allAppsNumCols = (availableWidthPx) /
                    (allAppsCellWidthPx + allAppsCellPaddingPx);
            allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
        }
    }
    我们可以看到行列数并不是固定的,是根据配置的行列数、图标大小、表格间距等计算出来的。如果我们想增加行列数,可以把图标缩小、间距加大,反之可以减小行列数。
    Launcher3根据不同的型号的手机加载不同的配置项,launcher3\src\main\java\com\android\launcher3\DynamicGrid.java,
        deviceProfiles.add(new DeviceProfile("Nexus 4",
                335, 567,  4, 4,  DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4));
        deviceProfiles.add(new DeviceProfile("Nexus 5",
                359, 567,  4, 4,  DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4));
    我用的测试机是Nexus 5,但实际使用的配置却是上面那个,这个我们就不管了。一共有十个参数,分别表示:设备名、最小宽度Dps、最小高度Dps、行数、列数、图标大小、图标字体大小、固定热键数目(Hotseat)、固定热键图标大小、默认Workspace布局。
    我们先将四列改成五列,
        deviceProfiles.add(new DeviceProfile("Nexus 4",
                335, 567,  4, 5,  DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4));
    测试后好像没什么变化,我们把图标再改小点,默认60,改成48,
    static float DEFAULT_ICON_SIZE_DP = 48;

   我们可以看到变成5列了,但是也变成6行了,我们在把最大行数设为5,原来是6,launcher3\src\main\res\values\config.xml,
    <integer name="config_dynamic_grid_max_long_edge_cell_count">6</integer>

    这样就变成5行5列了,但是看上去不大协调,目前我的测试机还是适合5*4,这里我们只是了解下怎么修改。

当然,除了背景、行列数可以改变外,我们也可以更改动画效果,这里就不在赘述了。

结束

时间: 2024-08-29 00:59:43

Launcher3--抽屉的相关文章

Launcher3源码浅析(5.1)--Workspace

目录 前言 初始化 布局 页面初始化 桌面图标 图标生成 图标拖动 图标点击效果 页面滑动 前言 Workspace是桌面的主要一个部分,一般设备(如手机)启动起来所看到的桌面的主要界面就是Workspace,在Launcher里其继承关系如下: Workspace->SmoothPagedView->PagedView->ViewGroup 所以可以说Workspace是一个视图容器类,容器里面主要放插件和应用快捷方式的图标.它负责桌面视图的布局工作,如桌面图标是多少行多少列:用户事件

Android launcher 桌面抽屉切换动画

版本:1.0 日期:2014.11.10 2014.11.11 版权:© 2014 kince 转载注明出处 一.概述 桌面抽屉之间的切换时Android用户经常触发的行为,好的交互会给用户一个舒适的体验.百度桌面默认是随机切换不同的动画,Android默认是一个大小和透明的渐变的动画,如下: 下面开始分析在Launcher2(KitKat)的源码里面是如何实现这种效果的. 二.下面列举相关的方法和变量 4082: interface LauncherTransitionable { View

第三方抽屉效果

1.  抽屉效果的基本原理应用了父子视图的层级,视图的位置改变,动画,手势操作等主要知识点.熟练掌握基础知识并灵活运用,即可实现该效果. > 父子视图的层级: 在指定层级上插入子视图 [view insertSubView: atIndex:] > 视图位置的改变: 通过视图的frame,center属性调整 > 动画:可使用UIView或CALayer的动画,这里主要使用了UIView的动画方法 [UIView animateWithDuration:……. ] > 手势操作:主

Android提高第十九篇之&quot;多方向&quot;抽屉--转

本文来自http://blog.csdn.net/hellogv/ ,引用必须注明出处! 在android上要实现类似Launch的抽屉效果,大家一定首先会想起SlidingDrawer.SlidingDrawer是android官方控件之一,本文的主角不是它,而是民间的控件工具集合~~~android-misc-widgets.android-misc-widgets里面包含几个widget:Panel.SmoothButton.Switcher.VirtualKeyboard,还有一些动画特

ios开发抽屉效果的封装使用

#import "DragerViewController.h" #define screenW [UIScreen mainScreen].bounds.size.width @interface DragerViewController () /** <#注释#> */ @property (nonatomic, weak) UIView *leftV; @property (nonatomic, weak) UIView *rightV; @property (non

动画的抽屉效果

添加三个View // // ViewController.m // UISenior17_抽屉效果 // // Created by lanou3g on 16/5/27. // Copyright © 2016年 张明杰. All rights reserved. // #import "ViewController.h" //frame #define XMGkeyPath(objc, keyPath) @(((void)objc.keyPath, #keyPath)) //获取

Android launcher3 开发初始篇

版本号:1.0 日期:2014.8.26 2014.8.27 2014.11.10 版权:© 2014 kince 转载注明出处 好久没有写博客,也是由于工作比較忙的关系.当然这不是理由,主要是非常多bug要改,而自己的效率又不是非常高.所以把非常多时间都浪费在修复bug上面了.闲话不多说,切入正题. Launcher3是最新的google官方Launcher.相比Launcher2,它具有更加小巧.流畅.清新等特点.所以选择它作为研究的对象.第一步当然是去下载其源代码.git网址是:https

MMDrawerController抽屉侧边栏的简单使用

1.MMDrawerController是一个简单实用的侧边栏第三方类库. 2.在appdelegate页中初始化你需要的左右侧边栏,leftViewController ,mainViewController. 3.在appdelegate中导入头文件#import "MMDrawerController.h" 4.初始化抽屉控制器:     MMDrawerController * drawerController = [[MMDrawerController alloc] ini

iOS开发——实用技术OC篇&amp;简单抽屉效果的实现

简单抽屉效果的实现 就目前大部分App来说基本上都有关于抽屉效果的实现,比如QQ/微信等.所以,今天我们就来简单的实现一下.当然如果你想你的效果更好或者是封装成一个到哪里都能用的工具类,那就还需要下一些功夫了,我们这里知识简单的介绍怎么去实现,不过一般我们开发都是找别人做好的,也没必要烂肺时间,除非你真的是大牛或者闲的蛋疼. 其实关于抽屉效果就是界面有三个View,其实一个主View其他两个分别是左边和右边的View,我们分别为他们添加手势,实现左右滑动显示对应的View. 一:所以,首先我们需

抽屉栏

首先倒入如下文件 #import "AppDelegate.h" #import "MainViewController.h" #import "LeftViewController.h" #import "MMDrawerController.h" #import "MMExampleDrawerVisualStateManager.h" #import "RightTableViewContr