【问题标题】:Material design color palette材料设计调色板
【发布时间】:2016-01-01 17:59:33
【问题描述】:

Google 设计了一个color palette。给定一种颜色,我想在 Android 中动态创建调色板。

Graphic Design site 和 an open source javascript solution 中有一个类似的问题,它生成了类似的调色板。每种颜色的因子在here 中找到,用于创建颜色的函数在this stackoverflow answer 中找到。

我使用该答案和项目来生成类似于 Google 的调色板。但是,我想要一种算法,它可以返回 Google 生成的确切值(请参阅第一个链接)。

问题:Google 如何计算材料设计的调色板颜色?


到目前为止我所做的尝试:

根据以上信息,我创建了这个示例来展示我如何生成类似的调色板。同样,我想要确切的值。

import android.app.Activity;
import android.app.AlertDialog;
import android.graphics.Color;
import android.os.AsyncTask;
import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;

import java.lang.ref.WeakReference;
import java.util.LinkedList;
import java.util.List;

/**
 * @author Jared Rummler <jared.rummler@gmail.com>
 */
public class PaletteTask extends AsyncTask<Integer, Void, List<PaletteTask.Shade>> {

  private static int shadeColor(int color, double percent) {
    return shadeColor(String.format("#%06X", (0xFFFFFF & color)), percent);
  }

  private static int shadeColor(String color, double percent) {
    long f = Long.parseLong(color.substring(1), 16);
    double t = percent < 0 ? 0 : 255;
    double p = percent < 0 ? percent * -1 : percent;
    long R = f >> 16;
    long G = f >> 8 & 0x00FF;
    long B = f & 0x0000FF;
    int red = (int) (Math.round((t - R) * p) + R);
    int green = (int) (Math.round((t - G) * p) + G);
    int blue = (int) (Math.round((t - B) * p) + B);
    return Color.rgb(red, green, blue);
  }

  private final WeakReference<Activity> activityWeakReference;

  private final List<Shade> shades = new LinkedList<>();

  {
    shades.add(new Shade(0.9, "50"));
    shades.add(new Shade(0.7, "100"));
    shades.add(new Shade(0.5, "200"));
    shades.add(new Shade(0.333, "300"));
    shades.add(new Shade(0.166, "400"));
    shades.add(new Shade(0, "500"));
    shades.add(new Shade(-0.125, "600"));
    shades.add(new Shade(-0.25, "700"));
    shades.add(new Shade(-0.375, "800"));
    shades.add(new Shade(-0.5, "900"));
    shades.add(new Shade(0.7, "A100"));
    shades.add(new Shade(0.5, "A200"));
    shades.add(new Shade(0.166, "A400"));
    shades.add(new Shade(-0.25, "A700"));
  }

  public PaletteTask(Activity activity) {
    activityWeakReference = new WeakReference<>(activity);
  }

  @Override protected List<Shade> doInBackground(Integer... colors) {

    for (Shade shade : shades) {
      shade.color = shadeColor(colors[0], shade.percent);
    }

    return shades;
  }

  @Override protected void onPostExecute(List<Shade> shades) {
    Activity activity = activityWeakReference.get();
    if (activity == null || activity.isFinishing()) {
      return;
    }

    // Create a dialog that shows our generated colors:

    ScrollView scrollView = new ScrollView(activity);
    LinearLayout linearLayout = new LinearLayout(activity);
    linearLayout.setOrientation(LinearLayout.VERTICAL);

    int width, height;
    width = LinearLayout.LayoutParams.MATCH_PARENT;
    height = (int) (30/*dp*/ * (activity.getResources().getDisplayMetrics().densityDpi / 160f));

    // add each color
    for (Shade shade : shades) {
      LinearLayout layoutColor = new LinearLayout(activity);
      TextView textView = new TextView(activity);

      layoutColor.setLayoutParams(new LinearLayout.LayoutParams(width, height));
      layoutColor.setBackgroundColor(shade.color);
      layoutColor.setGravity(Gravity.CENTER);

      textView.setText(shade.name + "    " + String.format("#%06X", (0xFFFFFF & shade.color)));

      layoutColor.addView(textView);
      linearLayout.addView(layoutColor);
    }

    scrollView.addView(linearLayout);

    new AlertDialog.Builder(activity).setView(scrollView).show();
  }

  public static class Shade {

    final double percent;
    final String name;
    int color;

    public Shade(double percent, String name) {
      this.percent = percent;
      this.name = name;
    }
  }

}

调用AsynTask:

int materialRed500 = 0xFFF44336;
new PaletteTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, materialRed500);

由上述代码创建的对话框:

【问题讨论】:

  • 很好奇为什么需要动态创建它;你不能把它作为应用程序中的资源吗?
  • @ChiefTwoPencils,是的,我可以将所有颜色作为资源。为什么?为了科学。 :)
  • 您从我的答案中使用的算法并非 100% 准确。它不使用 HSL 来变亮或变暗。 Google 的调色板可能使用适当的 HSL 转换来变亮(提高 L)。如果需要准确性,建议使用 TinyColor 对我的回答进行评论。我的是为了速度。
  • 此外,虽然 Google 调色板确实为每种原色提供 9 种强调色,但总共有 10 种。目前尚不清楚他们所做的阴影是否恰好是每级 10%,因为它会导致一个相信。我的意思是,他们列出的范围是 50-900... 不完全是 100-1000 我会说...您可以看到 900 并没有像我认为的 90% 那样接近黑色
  • 我打算写一个正确的答案,但后来我意识到我没有设置我的 Android Dev Env 并且无法对其进行测试或正确编码。但是在使用RGBToHSV 来转换它的某个地方......然后提高/降低B 或V 或L 或者他们在Android上称之为翻转...... 10%......然后使用HSVToColor 转换回来

标签: android colors material-design


【解决方案1】:

接受的答案中的逻辑是错误的。它不会为您生成真实的材质颜色。有官方材质调色板工具here。如果你愿意,你可以尝试和比较。

不幸的是,谷歌没有为上述工具提供库,这是一个压缩和打包的 javascript 文件,其中包含 redux。我和我的同事,我们做了一个逆向工程来得到只生成颜色的部分,你可以查看它here。请记住,这不是我的代码。

你可以试试here 使用以下行。

materialColorPicker('00ff00')

此调色板具有所选颜色的不同色调,它们是:50、100、200、300、400、500、600、700、800、900 但给定的颜色可以是这些色调中的任何一种。

例如,如果您选择红色,此调色板将为您提供以下设置。

50: "ffe7e6"
100: "ffc7b8"
200: "ffa28a"
300: "ff795b"
400: "ff5436"
500: "ff200c"
600: "ff1507"
700: "ff0000" // input colour
800: "f10000"
900: "da0000" 

但是如果你选择#17996b的颜色,调色板会产生以下值:

50: "e0f3ec"
100: "b4e0cf"
200: "84cdb1"
300: "54b893"
400: "32a97f"
500: "17996b" // input colour
600: "138c60"
700: "0d7c52"
800: "016c45"
900: "00502a"

如您所见,您认为是原色的输入颜色可以是任何生成的颜色。

【讨论】:

  • 请您发布未缩小的代码吗?上传到 GitHub?或者这是 material.io 的代码?
  • @JaredRummler 是的,正如我在回答中提到的,此代码属于 material.io 网站。但是我通过一些逆向工程转换成一个库。您可以使用 unminify.com 使其易于阅读。
  • 所以你从 material.io 中抽象出这段代码,但没有重新编写?是的,我使用了一个 unminify 工具,但希望你重新编写它,因为 un-minify 在某种程度上仍然非常模糊。感谢发帖。
  • @JaredRummler 是的,我从 material.io 中提取了它。我没有重写它。它有什么不同吗?它完成了这项工作。如果您检查 material.io 源,则此库具有 MIT 许可证。所以请随意使用它。
  • 我只是希望用 Java 或 Kotlin 重新编写它,并且不混淆变量/函数名称会很好。
【解决方案2】:

归根结底,您无法使用始终与相应的 Google Material Design 调色板完全匹配的单一颜色生成调色板。

这是因为调色板都遵循不同的颜色渐变。例如,红色调色板是使用以下进程生成的(使用 TinyColor.js 的 JS 代码,但您仍然可以看到 HSL 修改):

return [
            { hex : tinycolor( hex ).lighten( 37.7 ).saturate( 10.4 ).spin( -13 ).toHexString(), name : '50' },
            { hex : tinycolor( hex ).lighten( 31.8 ).saturate( 10.4 ).spin( -9.5 ).toHexString(), name : '100' },
            { hex : tinycolor( hex ).lighten( 18.7 ).desaturate( 17 ).spin( -3.9 ).toHexString(), name : '200' },
            { hex : tinycolor( hex ).lighten( 9.1 ).desaturate( 20.9 ).spin( -4 ).toHexString(), name : '300' },
            { hex : tinycolor( hex ).lighten( 4.1 ).desaturate( 6.6 ).spin( -3 ).toHexString(), name : '400' },
            { hex : hex, name : '500' },
            { hex : tinycolor( hex ).darken( 3.1 ).desaturate( 12.4 ).spin( -2.7 ).toHexString(), name: '600' },
            { hex : tinycolor( hex ).darken( 7.8 ).desaturate( 24.5 ).spin( -4 ).toHexString(), name: '700' },
            { hex : tinycolor( hex ).darken( 11.7 ).desaturate( 23.2 ).spin( -4 ).toHexString(), name: '800' },
            { hex : tinycolor( hex ).darken( 17 ).desaturate( 16.1 ).spin( -4 ).toHexString(), name: '900' },
            { hex : tinycolor( hex ).lighten( 16.7 ).saturate( 10.4 ).spin( 0.6 ).toHexString(), name: 'A100' },
            { hex : tinycolor( hex ).lighten( 7.7 ).saturate( 10.4 ).spin( -4 ).toHexString(), name: 'A200' },
            { hex : tinycolor( hex ).darken( 3.9 ).saturate( 10.4 ).spin( -15.5 ).toHexString(), name: 'A400' },
            { hex : tinycolor( hex ).darken( 16.6 ).saturate( 10.4 ).spin( -4 ).toHexString(), name: 'A700' }
        ];

但是,当您将相同的渐变应用到 Indigo 基色 (500) 时,您会发现调色板根本不匹配。在下图中,最左侧的调色板是默认的 MD 调色板,左侧第二个是使用上述进程生成的调色板。这两个调色板完全匹配。当我加载 MD Indigo 调色板(第三个调色板),然后使用 Indigo 500 值和 Red Palette 进程代码生成一个调色板时,它会创建第四个调色板。正如你所看到的,虽然这种进展对于红色来说是准确的,但对于其他颜色来说,它的方式很遥远:

可以在here 找到有关此主题的更多信息。 谷歌选择的颜色很可能是由设计师亲手挑选的,而不是通过程序生成的。

编辑:此外,该 MCG 的代码已完全修改。颜色的新逻辑可以在here找到,它使用tinycolor.js进行修改功能。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2016-04-04
  • 2017-09-20
  • 1970-01-01
  • 1970-01-01
  • 2019-02-28
  • 2015-08-15
  • 2015-08-13
  • 2019-02-15
相关资源
最近更新 更多