Android 7.1 修改副屏显示DPI(补充旋转补丁)

简介: Android 7.1 修改副屏显示DPI(补充旋转补丁)

平台


RK3288 + Android 7.1 + EDP x 2 (1080P)


问题描述


打开客户的双屏异显应用, 发现副屏的显示布局挤压错乱, 经过测试排查, 发现是副屏的DPI过高导致(320).


解决


frameworks/base/services/core/java/com/android/server/display/DisplayDeviceInfo.java

public void setAssumedDensityForExternalDisplay(int width, int height) {
        //修改DPI XHDPI(320) -> MEDIUM(160), 也可以设置为任意想要的值.
        densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_MEDIUM / 1080;
        // Technically, these values should be smaller than the apparent density
        // but we don't know the physical size of the display.
        xDpi = densityDpi;
        yDpi = densityDpi;
    }


旋转副屏补丁


主要问题可以通过dumpsys display和 dumpsys window中看出, 强制旋转后, 副屏显示的方向存在两个问题


主屏已旋转, 但副屏仍然保持横屏

虽然已旋转, 但副屏显示的尺寸为 1920x1080, 而不是 1080x1920, 从而导致显示不全等问题

补丁见资源.

解决双横屏强制旋转为双竖屏(旋转90度)

解决旋转90度后, 副屏显示不完整


调试


dump下显示信息:


adb shell dumpsys display
DISPLAY MANAGER (dumpsys display)
  mOnlyCode=false
  mSafeMode=false
  mPendingTraversal=false
  mGlobalDisplayState=ON
  mNextNonDefaultDisplayId=2
  mDefaultViewport=DisplayViewport{valid=true, displayId=0, orientation=0, logicalFrame=Rect(0, 0 - 1920, 1080), physicalFrame=Rect(0, 0 - 1920, 1080), deviceWidth=1920, deviceHeight=1080}
  mExternalTouchViewport=DisplayViewport{valid=true, displayId=0, orientation=0, logicalFrame=Rect(0, 0 - 1920, 1080), physicalFrame=Rect(0, 0 - 1920, 1080), deviceWidth=1920, deviceHeight=1080}
  mDefaultDisplayDefaultColorMode=0
  mSingleDisplayDemoMode=false
  mWifiDisplayScanRequestCount=0
Display Adapters: size=4
  LocalDisplayAdapter
  OverlayDisplayAdapter
    mCurrentOverlaySetting=
    mOverlays: size=0
  WifiDisplayAdapter
    mCurrentStatus=WifiDisplayStatus{featureState=2, scanState=0, activeDisplayState=0, activeDisplay=null, displays=[], sessionInfo=WifiDisplaySessionInfo:
        Client/Owner: Client
        GroupId: 
        Passphrase: 
        SessionId: 0
        IP Address: }
    mFeatureState=2
    mScanState=0
    mActiveDisplayState=0
    mActiveDisplay=null
    mDisplays=[]
    mAvailableDisplays=[]
    mRememberedDisplays=[]
    mPendingStatusChangeBroadcast=false
    mSupportsProtectedBuffers=false
    mDisplayController:
      mWifiDisplayOnSetting=false
      mWifiP2pEnabled=true
      mWfdEnabled=false
      mWfdEnabling=false
      mNetworkInfo=[type: WIFI_P2P[], state: UNKNOWN/IDLE, reason: (unspecified), extra: (none), failover: false, available: true, roaming: false, metered: false]
      mScanRequested=false
      mDiscoverPeersInProgress=false
      mDesiredDevice=null
      mConnectingDisplay=null
      mDisconnectingDisplay=null
      mCancelingDisplay=null
      mConnectedDevice=null
      mConnectionRetriesLeft=0
      mRemoteDisplay=null
      mRemoteDisplayInterface=null
      mRemoteDisplayConnected=false
      mAdvertisedDisplay=null
      mAdvertisedDisplaySurface=null
      mAdvertisedDisplayWidth=0
      mAdvertisedDisplayHeight=0
      mAdvertisedDisplayFlags=0
      mAvailableWifiDisplayPeers: size=0
  VirtualDisplayAdapter
Display Devices: size=2
  DisplayDeviceInfo{"内置屏幕": uniqueId="local:0", 1920 x 1080, modeId 1, defaultModeId 1, supportedModes [{id=1, width=1920, height=1080, fps=65.0}], colorMode 0, supportedColorModes [0], HdrCapabilities android.view.Display$HdrCapabilities@a69d6308, density 160, 378.046 x 160.421 dpi, appVsyncOff 1000000, presDeadline 15384615, touch INTERNAL, rotation 0, type BUILT_IN, state ON, FLAG_DEFAULT_DISPLAY, FLAG_ROTATES_WITH_CONTENT, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}
    mAdapter=LocalDisplayAdapter
    mUniqueId=local:0
    mDisplayToken=android.os.BinderProxy@997301b
    mCurrentLayerStack=0
    mCurrentOrientation=0
    mCurrentLayerStackRect=Rect(0, 0 - 1920, 1080)
    mCurrentDisplayRect=Rect(0, 0 - 1920, 1080)
    mCurrentSurface=null
    mBuiltInDisplayId=0
    mActivePhysIndex=0
    mActiveModeId=1
    mActiveColorMode=0
    mState=ON
    mBrightness=205
    mBacklight=com.android.server.lights.LightsService$LightImpl@5e9e5b8
    mDisplayInfos=
      PhysicalDisplayInfo{1920 x 1080, 65.0 fps, density 1.0, 378.046 x 160.421 dpi, secure true, appVsyncOffset 1000000, bufferDeadline 15384615}
    mSupportedModes=
      DisplayModeRecord{mMode={id=1, width=1920, height=1080, fps=65.0}}
    mSupportedColorModes=[0]
  DisplayDeviceInfo{"HDMI 屏幕": uniqueId="local:1", 1920 x 1080, modeId 2, defaultModeId 2, supportedModes [{id=2, width=1920, height=1080, fps=63.000004}], colorMode 0, supportedColorModes [0], HdrCapabilities android.view.Display$HdrCapabilities@a69d6308, density 320, 320.0 x 320.0 dpi, appVsyncOff 1000000, presDeadline 15873015, touch EXTERNAL, rotation 0, type HDMI, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_PRESENTATION}
    mAdapter=LocalDisplayAdapter
    mUniqueId=local:1
    mDisplayToken=android.os.BinderProxy@ce50f91
    mCurrentLayerStack=0
    mCurrentOrientation=0
    mCurrentLayerStackRect=Rect(0, 0 - 1920, 1080)
    mCurrentDisplayRect=Rect(0, 0 - 1920, 1080)
    mCurrentSurface=null
    mBuiltInDisplayId=1
    mActivePhysIndex=0
    mActiveModeId=2
    mActiveColorMode=0
    mState=ON
    mBrightness=-1
    mBacklight=null
    mDisplayInfos=
      PhysicalDisplayInfo{1920 x 1080, 63.000004 fps, density 1.33125, 320.0 x 320.0 dpi, secure true, appVsyncOffset 1000000, bufferDeadline 15873015}
    mSupportedModes=
      DisplayModeRecord{mMode={id=2, width=1920, height=1080, fps=63.000004}}
    mSupportedColorModes=[0]
Logical Displays: size=2
  Display 0:
    mDisplayId=0
    mLayerStack=0
    mHasContent=true
    mRequestedMode=0
    mRequestedColorMode=0
    mDisplayOffset=(0, 0)
    mPrimaryDisplayDevice=内置屏幕
    mBaseDisplayInfo=DisplayInfo{"内置屏幕", uniqueId "local:0", app 1920 x 1080, real 1920 x 1080, largest app 1920 x 1080, smallest app 1920 x 1080, mode 1, defaultMode 1, modes [{id=1, width=1920, height=1080, fps=65.0}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@a69d6308, rotation 0, density 160 (378.046 x 160.421) dpi, layerStack 0, appVsyncOff 1000000, presDeadline 15384615, type BUILT_IN, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}
    mOverrideDisplayInfo=DisplayInfo{"内置屏幕", uniqueId "local:0", app 1920 x 1024, real 1920 x 1080, largest app 1920 x 1840, smallest app 1080 x 1000, mode 1, defaultMode 1, modes [{id=1, width=1920, height=1080, fps=65.0}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@a69d6308, rotation 0, density 160 (378.046 x 160.421) dpi, layerStack 0, appVsyncOff 1000000, presDeadline 15384615, type BUILT_IN, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}
  Display 1:
    mDisplayId=1
    mLayerStack=1
    mHasContent=false
    mRequestedMode=0
    mRequestedColorMode=0
    mDisplayOffset=(0, 0)
    mPrimaryDisplayDevice=HDMI 屏幕
    mBaseDisplayInfo=DisplayInfo{"HDMI 屏幕", uniqueId "local:1", app 1920 x 1080, real 1920 x 1080, largest app 1920 x 1080, smallest app 1920 x 1080, mode 2, defaultMode 2, modes [{id=2, width=1920, height=1080, fps=63.000004}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@a69d6308, rotation 0, density 320 (320.0 x 320.0) dpi, layerStack 1, appVsyncOff 1000000, presDeadline 15873015, type HDMI, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_PRESENTATION}
    mOverrideDisplayInfo=DisplayInfo{"HDMI 屏幕", uniqueId "local:1", app 1920 x 1080, real 1920 x 1080, largest app 1920 x 1080, smallest app 1920 x 1080, mode 2, defaultMode 2, modes [{id=2, width=1920, height=1080, fps=63.000004}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@a69d6308, rotation 0, density 320 (320.0 x 320.0) dpi, layerStack 1, appVsyncOff 1000000, presDeadline 15873015, type HDMI, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_PRESENTATION}


从上面的信息可以看得, 副屏HDMI 屏幕使用的是320的DPI.


分析排查


从dump出来的信息着手, 查找相关的变量PhysicalDisplayInfo信息来源:


frameworks/base/services/core/java/com/android/server/display/LocalDisplayAdapter.java
    private void tryConnectDisplayLocked(int builtInDisplayId) {
        IBinder displayToken = SurfaceControl.getBuiltInDisplay(builtInDisplayId);
        if (displayToken != null) {
            SurfaceControl.PhysicalDisplayInfo[] configs =
                    SurfaceControl.getDisplayConfigs(displayToken);
    }


frameworks/base/core/java/android/view/SurfaceControl.java

public static SurfaceControl.PhysicalDisplayInfo[] getDisplayConfigs(IBinder displayToken) {
        if (displayToken == null) {
            throw new IllegalArgumentException("displayToken must not be null");
        }
        return nativeGetDisplayConfigs(displayToken);
    }
    private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs(
            IBinder displayToken);


frameworks/base/core/jni/android_view_SurfaceControl.cpp

static jobjectArray nativeGetDisplayConfigs(JNIEnv* env, jclass clazz,
        jobject tokenObj) {
    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
    if (token == NULL) return NULL;
    Vector<DisplayInfo> configs;
    if (SurfaceComposerClient::getDisplayConfigs(token, &configs) != NO_ERROR ||
            configs.size() == 0) {
        return NULL;
    }
  //...
}


frameworks/native/libs/gui/SurfaceComposerClient.cpp

status_t SurfaceComposerClient::getDisplayConfigs(
        const sp<IBinder>& display, Vector<DisplayInfo>* configs)
{
    return ComposerService::getComposerService()->getDisplayConfigs(display, configs);
}
void ComposerService::connectLocked() {
    const String16 name("SurfaceFlinger");
    while (getService(name, &mComposerService) != NO_ERROR) {
        usleep(250000);
    }
    assert(mComposerService != NULL);
    // Create the death listener.
    class DeathObserver : public IBinder::DeathRecipient {
        ComposerService& mComposerService;
        virtual void binderDied(const wp<IBinder>& who) {
            ALOGW("ComposerService remote (surfaceflinger) died [%p]",
                  who.unsafe_get());
            mComposerService.composerServiceDied();
        }
     public:
        DeathObserver(ComposerService& mgr) : mComposerService(mgr) { }
    };
    mDeathObserver = new DeathObserver(*const_cast<ComposerService*>(this));
    IInterface::asBinder(mComposerService)->linkToDeath(mDeathObserver);
}


几经跳转, 获取的是SurfaceFlinger服务.


frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

status_t SurfaceFlinger::getDisplayConfigs(const sp<IBinder>& display,
        Vector<DisplayInfo>* configs) {
    if ((configs == NULL) || (display.get() == NULL)) {
        return BAD_VALUE;
    }
    if (!display.get())
        return NAME_NOT_FOUND;
    int32_t type = NAME_NOT_FOUND;
    for (int i=0 ; i<DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES ; i++) {
        if (display == mBuiltinDisplays[i]) {
            type = i;
            break;
        }
    }
    if (type < 0) {
        return type;
    }
    // TODO: Not sure if display density should handled by SF any longer
    class Density {
        static int getDensityFromProperty(char const* propName) {
            char property[PROPERTY_VALUE_MAX];
            int density = 0;
            if (property_get(propName, property, NULL) > 0) {
                density = atoi(property);
            }
            return density;
        }
    public:
        static int getEmuDensity() {
            return getDensityFromProperty("qemu.sf.lcd_density"); }
        static int getBuildDensity()  {
            return getDensityFromProperty("ro.sf.lcd_density"); }
    };
    configs->clear();
    for (const auto& hwConfig : getHwComposer().getConfigs(type)) {
        DisplayInfo info = DisplayInfo();
        float xdpi = hwConfig->getDpiX();
        float ydpi = hwConfig->getDpiY();
        if (type == DisplayDevice::DISPLAY_PRIMARY) {
            // The density of the device is provided by a build property
            float density = Density::getBuildDensity() / 160.0f;
            if (density == 0) {
                // the build doesn't provide a density -- this is wrong!
                // use xdpi instead
                ALOGE("ro.sf.lcd_density must be defined as a build property");
                density = xdpi / 160.0f;
            }
            if (Density::getEmuDensity()) {
                // if "qemu.sf.lcd_density" is specified, it overrides everything
                xdpi = ydpi = density = Density::getEmuDensity();
                density /= 160.0f;
            }
            info.density = density;
            // TODO: this needs to go away (currently needed only by webkit)
            sp<const DisplayDevice> hw(getDefaultDisplayDevice());
            info.orientation = hw->getOrientation();
        } else {
            // TODO: where should this value come from?
            static const int TV_DENSITY = 213;
            info.density = TV_DENSITY / 160.0f;
            info.orientation = 0;
        }
        info.w = hwConfig->getWidth();
        info.h = hwConfig->getHeight();
        info.xdpi = xdpi;
        info.ydpi = ydpi;
        info.fps = 1e9 / hwConfig->getVsyncPeriod();
        info.appVsyncOffset = VSYNC_EVENT_PHASE_OFFSET_NS;
        // This is how far in advance a buffer must be queued for
        // presentation at a given time.  If you want a buffer to appear
        // on the screen at time N, you must submit the buffer before
        // (N - presentationDeadline).
        //
        // Normally it's one full refresh period (to give SF a chance to
        // latch the buffer), but this can be reduced by configuring a
        // DispSync offset.  Any additional delays introduced by the hardware
        // composer or panel must be accounted for here.
        //
        // We add an additional 1ms to allow for processing time and
        // differences between the ideal and actual refresh rate.
        info.presentationDeadline = hwConfig->getVsyncPeriod() -
                SF_VSYNC_EVENT_PHASE_OFFSET_NS + 1000000;
        // All non-virtual displays are currently considered secure.
        info.secure = true;
        configs->push_back(info);
    }
    return NO_ERROR;
}


frameworks/native/services/surfaceflinger/SurfaceFlinger.h

HWComposer& getHwComposer() const { return *mHwc; }


frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

void SurfaceFlinger::init() {
  mHwc = new HWComposer(this);
  }


frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.cpp

HWComposer::HWComposer(const sp<SurfaceFlinger>& flinger)
    : mFlinger(flinger),
      mAdapter(),
      mHwcDevice(),
      mDisplayData(2),
      mFreeDisplaySlots(),
      mHwcDisplaySlots(),
      mCBContext(),
      mEventHandler(nullptr),
      mVSyncCounts(),
      mRemainingHwcVirtualDisplays(0)
{
    for (size_t i=0 ; i<HWC_NUM_PHYSICAL_DISPLAY_TYPES ; i++) {
        mLastHwVSync[i] = 0;
        mVSyncCounts[i] = 0;
    }
    loadHwcModule();
}
// Load and prepare the hardware composer module.  Sets mHwc.
void HWComposer::loadHwcModule()
{
    ALOGV("loadHwcModule");
    hw_module_t const* module;
    if (hw_get_module(HWC_HARDWARE_MODULE_ID, &module) != 0) {
        ALOGE("%s module not found, aborting", HWC_HARDWARE_MODULE_ID);
        abort();
    }
    hw_device_t* device = nullptr;
    int error = module->methods->open(module, HWC_HARDWARE_COMPOSER, &device);
    if (error != 0) {
        ALOGE("Failed to open HWC device (%s), aborting", strerror(-error));
        abort();
    }
    uint32_t majorVersion = (device->version >> 24) & 0xF;
    if (majorVersion == 2) {
        mHwcDevice = std::make_unique<HWC2::Device>(
                reinterpret_cast<hwc2_device_t*>(device));
    } else {
        mAdapter = std::make_unique<HWC2On1Adapter>(
                reinterpret_cast<hwc_composer_device_1_t*>(device));
        uint8_t minorVersion = mAdapter->getHwc1MinorVersion();
        if (minorVersion < 1) {
            ALOGE("Cannot adapt to HWC version %d.%d",
                    static_cast<int32_t>((minorVersion >> 8) & 0xF),
                    static_cast<int32_t>(minorVersion & 0xF));
            abort();
        }
        mHwcDevice = std::make_unique<HWC2::Device>(
                static_cast<hwc2_device_t*>(mAdapter.get()));
    }
    mRemainingHwcVirtualDisplays = mHwcDevice->getMaxVirtualDisplayCount();
}


加载HW库(/system/lib/hw/hwcomposer.rk30board.so)


hardware/rockchip/hwcomposer/hwcomposer.cpp
static float getDefaultDensity(uint32_t width, uint32_t height) {
    // Default density is based on TVs: 1080p displays get XHIGH density,
    // lower-resolution displays get TV density. Maybe eventually we'll need
    // to update it for 4K displays, though hopefully those just report
    // accurate DPI information to begin with. This is also used for virtual
    // displays and even primary displays with older hwcomposers, so be
    // careful about orientation.
    uint32_t h = width < height ? width : height;
    if (h >= 1080) return ACONFIGURATION_DENSITY_XHIGH;//320
    else           return ACONFIGURATION_DENSITY_TV;//213
}
static int hwc_get_display_attributes(struct hwc_composer_device_1 *dev,
                                      int display, uint32_t config,
                                      const uint32_t *attributes,
                                      int32_t *values) {
  UN_USED(config);
  struct hwc_context_t *ctx = (struct hwc_context_t *)&dev->common;
  DrmConnector *c = ctx->drm.GetConnectorFromType(display);
  if (!c) {
    ALOGE("Failed to get DrmConnector for display %d", display);
    return -ENODEV;
  }
  hwc_drm_display_t *hd = &ctx->displays[c->display()];
  if (!hd->active)
    return -ENODEV;
  uint32_t mm_width = c->mm_width();
  uint32_t mm_height = c->mm_height();
  int w = hd->framebuffer_width;
  int h = hd->framebuffer_height;
  int vrefresh = hd->vrefresh;
  for (int i = 0; attributes[i] != HWC_DISPLAY_NO_ATTRIBUTE; ++i) {
    switch (attributes[i]) {
      case HWC_DISPLAY_VSYNC_PERIOD:
        values[i] = 1000 * 1000 * 1000 / vrefresh;
        break;
      case HWC_DISPLAY_WIDTH:
        values[i] = w;
        break;
      case HWC_DISPLAY_HEIGHT:
        values[i] = h;
        break;
      case HWC_DISPLAY_DPI_X:
        /* Dots per 1000 inches */
        values[i] = mm_width ? (w * UM_PER_INCH) / mm_width : getDefaultDensity(w,h)*1000;
        break;
      case HWC_DISPLAY_DPI_Y:
        /* Dots per 1000 inches */
        values[i] =
            mm_height ? (h * UM_PER_INCH) / mm_height : getDefaultDensity(w,h)*1000;
        break;
    }
  }
  return 0;
}


if (h >= 1080) return ACONFIGURATION_DENSITY_XHIGH;//320这部分代码, 直接返回了320的DPI

尝试修改为ACONFIGURATION_DENSITY_MEDIUM, 成功把PhysicalDisplayInfo修改为160 dpi

但是DisplayDeviceInfo中的DPI依然是320, 那么问题并不是出在PhysicalDisplayInfo


回过头, 重新看看dump的函数:


frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java

private void dumpInternal(PrintWriter pw) {
        pw.println("DISPLAY MANAGER (dumpsys display)");
  //...
            pw.println("Display Devices: size=" + mDisplayDevices.size());
            for (DisplayDevice device : mDisplayDevices) {
                pw.println("  " + device.getDisplayDeviceInfoLocked());
                device.dumpLocked(ipw);
            }
      //....
      }


frameworks/base/services/core/java/com/android/server/display/DisplayDevice.java

/**
     * Gets information about the display device.
     *
     * The information returned should not change between calls unless the display
     * adapter sent a {@link DisplayAdapter#DISPLAY_DEVICE_EVENT_CHANGED} event and
     * {@link #applyPendingDisplayDeviceInfoChangesLocked()} has been called to apply
     * the pending changes.
     *
     * @return The display device info, which should be treated as immutable by the caller.
     * The display device should allocate a new display device info object whenever
     * the data changes.
     */
    public abstract DisplayDeviceInfo getDisplayDeviceInfoLocked();


frameworks/base/services/core/java/com/android/server/display/LocalDisplayAdapter.java

private final class LocalDisplayDevice extends DisplayDevice
        @Override
        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
            if (mInfo == null) {
                SurfaceControl.PhysicalDisplayInfo phys = mDisplayInfos[mActivePhysIndex];
                mInfo = new DisplayDeviceInfo();
                mInfo.width = phys.width;
    //...
                    mInfo.name = getContext().getResources().getString(
                            com.android.internal.R.string.display_manager_hdmi_display_name);
                    mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
                    mInfo.setAssumedDensityForExternalDisplay(phys.width, phys.height);
  }
  }


frameworks/base/services/core/java/com/android/server/display/DisplayDeviceInfo.java

public void setAssumedDensityForExternalDisplay(int width, int height) {
        densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080;
        // Technically, these values should be smaller than the apparent density
        // but we don't know the physical size of the display.
        xDpi = densityDpi;
        yDpi = densityDpi;
    }


找到了setAssumedDensityForExternalDisplay, 这熟悉的味道, 跟HW很像…


相关文章
|
20天前
|
XML Java Android开发
Android Studio App开发之对图片进行简单加工(包括放缩,旋转等等 附源码)
Android Studio App开发之对图片进行简单加工(包括放缩,旋转等等 附源码)
55 0
|
20天前
|
Android开发
Android 支持 ap6236 wifi 模块补丁
Android 支持 ap6236 wifi 模块补丁
29 0
|
20天前
|
API Android开发 开发者
【Android App】Vulkan实现宇宙中旋转雷达动画效果(附源码和原始视频 超详细必看)
【Android App】Vulkan实现宇宙中旋转雷达动画效果(附源码和原始视频 超详细必看)
90 1
|
20天前
|
XML 小程序 Java
【Android App】给三维魔方贴图以及旋转动画讲解和实战(附源码和演示视频 超详细必看)
【Android App】给三维魔方贴图以及旋转动画讲解和实战(附源码和演示视频 超详细必看)
40 0
|
20天前
|
XML Java Android开发
Android App事件交互中辨别缩放与旋转手指的讲解与实战(附源码 可直接使用)
Android App事件交互中辨别缩放与旋转手指的讲解与实战(附源码 可直接使用)
55 0
|
9月前
|
前端开发 Android开发 开发者
Android平台RTSP、RTMP播放端如何实现YUV或ARGB数据按设定角度旋转
做音视频RTSP或RTMP直播播放器的时候,不免会遇到这样的诉求,实时播放或快照的时候,由于前端摄像头安装角度不一定是正向,导致播放或快照的时候,视频view显示的画面是呈90° 180°甚至270°旋转的。
138 0
|
10月前
|
Android开发
Android 屏幕发生旋转对应的生命周期发生变化解析
Android 屏幕发生旋转对应的生命周期发生变化解析
134 0
|
Android开发
关闭安卓系统导航栏右下角自动旋转按钮
关闭安卓系统导航栏右下角自动旋转按钮
271 0
|
API
RK3326 android10.0(Q) 预装APP可卸载不恢复补丁
RK3326 android10.0(Q) 预装APP可卸载不恢复补丁
254 0
|
Java Android开发
Android 7.1 解决开机动画旋转问题
Android 7.1 解决开机动画旋转问题
146 0
Android 7.1 解决开机动画旋转问题