【问题标题】:Android 5 Lollipop shadow direction or y offsetAndroid 5 Lollipop 阴影方向或 y 偏移
【发布时间】:2014-12-20 18:13:09
【问题描述】:

是否有可能改变Y偏移的阴影方向?

现在我有以下布局

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container"
    android:layout_width="match_parent" android:layout_height="match_parent"
    tools:context=".MainActivity" tools:ignore="MergeRootFrame" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginBottom="200dp"
        android:elevation="5dp"
        android:translationZ="5dp"
        android:background="@android:color/darker_gray"
        android:layout_gravity="bottom"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginBottom="100dp"
        android:elevation="4dp"
        android:translationZ="5dp"
        android:background="@android:color/holo_blue_dark"
        android:layout_gravity="bottom"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:elevation="3dp"
        android:translationZ="5dp"
        android:background="@android:color/holo_green_dark"
        android:layout_gravity="bottom"/>

</FrameLayout>

再现了左边的结果,但我喜欢在右边的图像上实现效果

谁能指点我如何做到这一点?

【问题讨论】:

  • 不,您不能更改灯光位置。它已在所有应用程序中修复,以提供视觉上一致的照明模型。您是否考虑过重新排列图层,使最顶层的海拔最低,如右图所示?
  • 是的,但是视图下方没有显示阴影

标签: android android-5.0-lollipop material-design shadows


【解决方案1】:

API 21 中添加的View 上的setOutlineProvider 方法可能有用。来自文档:

设置视图的ViewOutlineProvider,生成定义其投射阴影形状的轮廓,并启用轮廓裁剪。

它确实允许自定义形状,但我不能 100% 确定可以改变阴影方向。

【讨论】:

    【解决方案2】:

    经过一些研究,我发现您无法设置阴影的 X 和 Y 偏移量,因此我想出了一个看起来不错的解决方案,它可以提供所需的效果,它由一个图层列表组成,但第一层将是一个渐变给一个好看的阴影效果,像这样:

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    
    <item android:gravity="top" >
    
        <shape  android:shape="rectangle">
    
            <size
                android:height="15dp"/>
            <gradient
                android:angle="90"
                android:startColor="@android:color/black"/>
    
        </shape>
    
    
    
    </item>
    <item
        android:top="15dp">
    
        <shape  android:shape="rectangle">
    
            <solid android:color="@android:color/white" />
    
        </shape>
    
    
    
    </item>
    </layer-list>
    

    第二层的顶部值和第一层的形状高度需要相同,但您可以修改它们的值以达到所需的高度,然后您只需将其设置为视图的背景您要应用高程的。也可以用作前棒棒糖设备的海拔替代方案。

    还要确保将与 xml 中指示的高度大小相等的 top_padding 添加到您将其作为背景应用到的视图中。

    注意:只有当您的视图占据整个屏幕宽度时,这才会看起来不错,因为侧面没有应用阴影。

    【讨论】:

      【解决方案3】:

      我发现创建顶部阴影的唯一方法是修改 Android 兼容性 v7 CardView 项目的一些源代码。该项目将 CardView 类引入到较早的 Android 版本,因此还包括高程阴影。生成的阴影非常接近“真实”的高程阴影。

      CardView 项目中的重要类是RoundRectDrawableWithShadow

      我这样称呼它:

      float density = getResources().getDisplayMetrics().density;
      float elevation = 2;
      xlayout.setBackgroundDrawable(new RoundRectDrawableWithShadow(
              getResources(), Color.BLACK, 0, 
              elevation*density, ((elevation +1 )*density) + 1
      ));
      

      res/values/values.xml 中需要以下内容

      <color name="cardview_shadow_end_color">#03000000</color>
      <color name="cardview_shadow_start_color">#47000000</color>
      <dimen name="cardview_compat_inset_shadow">1dp</dimen>
      

      这里修改类RoundRectDrawableWithShadow

      /*
       * Copyright (C) 2014 The Android Open Source Project
       *
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *      http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License.
       */
      package xxx;
      
      import android.content.res.Resources;
      import android.graphics.Canvas;
      import android.graphics.ColorFilter;
      import android.graphics.LinearGradient;
      import android.graphics.Paint;
      import android.graphics.Path;
      import android.graphics.PixelFormat;
      import android.graphics.RadialGradient;
      import android.graphics.Rect;
      import android.graphics.RectF;
      import android.graphics.Shader;
      import android.graphics.drawable.Drawable;
      import xxx.R;
      
      /**
       * A rounded rectangle drawable which also includes a shadow around.
       */
      public class RoundRectDrawableWithShadow extends Drawable {
          // used to calculate content padding
          final static double COS_45 = Math.cos(Math.toRadians(45));
      
          final static float SHADOW_MULTIPLIER = 1.5f;
      
          final int mInsetShadow; // extra shadow to avoid gaps between card and shadow
      
          /*
          * This helper is set by CardView implementations.
          * <p>
          * Prior to API 17, canvas.drawRoundRect is expensive; which is why we need this interface
          * to draw efficient rounded rectangles before 17.
          * */
          static RoundRectHelper sRoundRectHelper;
      
          Paint mPaint;
      
          Paint mCornerShadowPaint;
      
          Paint mEdgeShadowPaint;
      
          final RectF mCardBounds;
      
          float mCornerRadius;
      
          Path mCornerShadowPath;
      
          // updated value with inset
          float mMaxShadowSize;
      
          // actual value set by developer
          float mRawMaxShadowSize;
      
          // multiplied value to account for shadow offset
          float mShadowSize;
      
          // actual value set by developer
          float mRawShadowSize;
      
          private boolean mDirty = true;
      
          private final int mShadowStartColor;
      
          private final int mShadowEndColor;
      
          private boolean mAddPaddingForCorners = true;
      
          /**
           * If shadow size is set to a value above max shadow, we print a warning
           */
          private boolean mPrintedShadowClipWarning = false;
      
          public RoundRectDrawableWithShadow(
                  Resources resources, int backgroundColor, float radius,
                  float shadowSize, float maxShadowSize
          ) {
              mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color);
              mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color);
              mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow);
              mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
              mPaint.setColor(backgroundColor);
              mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
              mCornerShadowPaint.setStyle(Paint.Style.FILL);
              mCornerRadius = (int) (radius + .5f);
              mCardBounds = new RectF();
              mEdgeShadowPaint = new Paint(mCornerShadowPaint);
              mEdgeShadowPaint.setAntiAlias(false);
              setShadowSize(shadowSize, maxShadowSize);
      
              RoundRectDrawableWithShadow.sRoundRectHelper
                      = new RoundRectDrawableWithShadow.RoundRectHelper() {
                  @Override
                  public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
                          Paint paint) {
                      canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
                  }
              };
          }
      
          /**
           * Casts the value to an even integer.
           */
          private int toEven(float value) {
              int i = (int) (value + .5f);
              if (i % 2 == 1) {
                  return i - 1;
              }
              return i;
          }
      
          public void setAddPaddingForCorners(boolean addPaddingForCorners) {
              mAddPaddingForCorners = addPaddingForCorners;
              invalidateSelf();
          }
      
          @Override
          public void setAlpha(int alpha) {
              mPaint.setAlpha(alpha);
              mCornerShadowPaint.setAlpha(alpha);
              mEdgeShadowPaint.setAlpha(alpha);
          }
      
          @Override
          protected void onBoundsChange(Rect bounds) {
              super.onBoundsChange(bounds);
              mDirty = true;
          }
      
          void setShadowSize(float shadowSize, float maxShadowSize) {
              if (shadowSize < 0 || maxShadowSize < 0) {
                  throw new IllegalArgumentException("invalid shadow size");
              }
              shadowSize = toEven(shadowSize);
              maxShadowSize = toEven(maxShadowSize);
              if (shadowSize > maxShadowSize) {
                  shadowSize = maxShadowSize;
                  if (!mPrintedShadowClipWarning) {
                      mPrintedShadowClipWarning = true;
                  }
              }
              if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
                  return;
              }
              mRawShadowSize = shadowSize;
              mRawMaxShadowSize = maxShadowSize;
              mShadowSize = (int)(shadowSize * SHADOW_MULTIPLIER + mInsetShadow + .5f);
              mMaxShadowSize = maxShadowSize + mInsetShadow;
              mDirty = true;
              invalidateSelf();
          }
      
          @Override
          public boolean getPadding(Rect padding) {
              int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
                      mAddPaddingForCorners));
      //        int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
      //                mAddPaddingForCorners));
      //        padding.set(hOffset, vOffset, hOffset, vOffset);
              padding.set(0, vOffset, 0, 0);
              return true;
          }
      
          static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
                  boolean addPaddingForCorners) {
              if (addPaddingForCorners) {
                  return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
              } else {
                  return maxShadowSize * SHADOW_MULTIPLIER;
              }
          }
      
          static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
                  boolean addPaddingForCorners) {
              if (addPaddingForCorners) {
                  return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
              } else {
                  return maxShadowSize;
              }
          }
      
          @Override
          public void setColorFilter(ColorFilter cf) {
              mPaint.setColorFilter(cf);
              mCornerShadowPaint.setColorFilter(cf);
              mEdgeShadowPaint.setColorFilter(cf);
          }
      
          @Override
          public int getOpacity() {
              return PixelFormat.TRANSLUCENT;
          }
      
          void setCornerRadius(float radius) {
              radius = (int) (radius + .5f);
              if (mCornerRadius == radius) {
                  return;
              }
              mCornerRadius = radius;
              mDirty = true;
              invalidateSelf();
          }
      
          @Override
          public void draw(Canvas canvas) {
              if (mDirty) {
                  buildComponents(getBounds());
                  mDirty = false;
              }
              canvas.translate(0, -mRawShadowSize / 2);
              drawShadow(canvas);
              canvas.translate(0, +mRawShadowSize / 2);
              sRoundRectHelper.drawRoundRect(canvas, mCardBounds, mCornerRadius, mPaint);
          }
      
          private void drawShadow(Canvas canvas) {
              final float edgeShadowTop = -mCornerRadius - mShadowSize;
              final float insetVertical = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
              final float insetHorizontal = -mInsetShadow;
              // LT top
              int saved = canvas.save();
              canvas.translate(mCardBounds.left + insetHorizontal, mCardBounds.top + insetVertical);
              canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
              canvas.drawRect(0, edgeShadowTop,
                      mCardBounds.width() - 2 * insetHorizontal, -mCornerRadius + mShadowSize,
                      mEdgeShadowPaint);
              canvas.restoreToCount(saved);
      
              // RT right
              saved = canvas.save();
              canvas.translate(mCardBounds.right - insetHorizontal, mCardBounds.top + insetVertical);
              canvas.rotate(90f);
              canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
              canvas.restoreToCount(saved);
          }
      
          private void buildShadowCorners() {
              RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
              RectF outerBounds = new RectF(innerBounds);
              outerBounds.inset(-mShadowSize, -mShadowSize);
      
              if (mCornerShadowPath == null) {
                  mCornerShadowPath = new Path();
              } else {
                  mCornerShadowPath.reset();
              }
              mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
              mCornerShadowPath.moveTo(-mCornerRadius, 0);
              mCornerShadowPath.rLineTo(-mShadowSize, 0);
              // outer arc
              mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
              // inner arc
              mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
              mCornerShadowPath.close();
              float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
              mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
                      new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
                      new float[]{0f, startRatio, 1f}
                      , Shader.TileMode.CLAMP));
      
              // we offset the content shadowSize/2 pixels up to make it more realistic.
              // this is why edge shadow shader has some extra space
              // When drawing bottom edge shadow, we use that extra space.
              mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
                      -mCornerRadius - mShadowSize,
                      new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
                      new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
              mEdgeShadowPaint.setAntiAlias(false);
          }
      
          private void buildComponents(Rect bounds) {
              // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
              // We could have different top-bottom offsets to avoid extra gap above but in that case
              // center aligning Views inside the CardView would be problematic.
              final float verticalOffset = mRawMaxShadowSize * SHADOW_MULTIPLIER;
              mCardBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset,
                      bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset);
              buildShadowCorners();
          }
      
          float getCornerRadius() {
              return mCornerRadius;
          }
      
          void getMaxShadowAndCornerPadding(Rect into) {
              getPadding(into);
          }
      
          void setShadowSize(float size) {
              setShadowSize(size, mRawMaxShadowSize);
          }
      
          void setMaxShadowSize(float size) {
              setShadowSize(mRawShadowSize, size);
          }
      
          float getShadowSize() {
              return mRawShadowSize;
          }
      
          float getMaxShadowSize() {
              return mRawMaxShadowSize;
          }
      
          float getMinWidth() {
              final float content = 2 *
                      Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
              return content + (mRawMaxShadowSize + mInsetShadow) * 2;
          }
      
          float getMinHeight() {
              final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
                              + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
              return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
          }
      
          public void setColor(int color) {
              mPaint.setColor(color);
              invalidateSelf();
          }
      
          static interface RoundRectHelper {
              void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint);
          }
      }
      

      工作对我来说就像一个魅力。

      【讨论】:

      • 阴影是向上投射的,但后面有一个白色的背景,应该是透明的......
      • 你能发布你的完整布局xml吗?
      • 我发布了我的full question here。谢谢。
      • 你用什么值来表示密度?
      • 关于密度,它是特定于设备的:float density = getResources().getDisplayMetrics().density;
      猜你喜欢
      • 2016-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-25
      • 2013-05-25
      • 2021-10-29
      • 2011-08-30
      • 1970-01-01
      相关资源
      最近更新 更多