图像顶部的波纹效果 - Android

Posted

技术标签:

【中文标题】图像顶部的波纹效果 - Android【英文标题】:Ripple effect on top of Image - Android 【发布时间】:2015-03-06 23:34:38 【问题描述】:

我一直在我最近的业余项目中试验波纹动画。我很难找到一个“优雅”的解决方案来在某些情况下使用它来处理触摸事件。即图像,尤其是在列表、网格和回收视图中。动画几乎总是在视图后面进行动画处理,而不是在视图之上。这在 Buttons 和 TextViews 中不是问题,但如果您有图像的 GridView,波纹会出现在实际图像的后面或下方。显然这不是我想要的,虽然有一些我认为可以解决的解决方案,但我希望有一些我不知道的更简单的方法。

我使用以下代码来实现带有图像的自定义网格视图。我会给出完整的代码CLICK HERE,如果你愿意的话,你可以跟随。

现在是重要的东西。为了让我的图像在触摸时产生动画,我需要这个

button_ripple.xml

<ripple
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="@color/cream_background">
    <item>
        <selector xmlns:android="http://schemas.android.com/apk/res/android">
            <!-- Pressed -->
            <item
                android:drawable="@color/button_selected"
                android:state_pressed="true"/>
            <!-- Selected -->
            <item
                android:drawable="@color/button_selected"
                android:state_selected="true"/>
            <!-- Focus -->
            <item
                android:drawable="@color/button_selected"
                android:state_focused="true"/>
            <!-- Default -->
            <item android:drawable="@color/transparent"/>
        </selector>
    </item>
</ripple>

custom_grid.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_
              android:layout_
              android:gravity="center_horizontal"
              android:orientation="horizontal">

    <ImageView
        android:id="@+id/sceneGridItem"
        android:layout_
        android:layout_
        android:background="@drawable/button_ripple"
        android:gravity="center_horizontal"/>

</LinearLayout>

activity_main.xml

<GridView
    android:id="@+id/sceneGrid"
    android:layout_marginTop="15dp"
    android:layout_
    android:layout_
    android:verticalSpacing="15dp"
    android:numColumns="5" />

所有魔法和问题都发生在我设置背景的时候。虽然这确实在我的 imageview 上给了我一个涟漪动画,但它在 imageview 后面 动画。我希望动画出现在图像的顶部。所以我尝试了一些不同的东西,比如

将整个网格背景设置为 button_ripple。

<GridView
    android:id="@+id/sceneGrid"
    android:layout_marginTop="15dp"
    android:layout_
    android:layout_
    android:verticalSpacing="15dp"
    android:background="@drawable/button_ripple"
    android:numColumns="5" />

它完全符合您的想法,现在整个网格有一个半透明的背景,无论我按什么图像,整个网格都会从网格的中心开始动画。虽然这很酷,但这不是我想要的。

将根/父背景设置为 button_ripple。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_
                  android:layout_
                  android:gravity="center_horizontal"
                  android:background="@drawable/button_ripple"
                  android:orientation="horizontal">

该区域现在变大了,并填满了网格的整个单元格(不仅仅是图像),但它并没有把它带到前面。

将 custom_grid.xml 更改为 RelativeLayout 并将两个 ImageView 放在一起

custom_grid.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_
              android:layout_
              android:orientation="horizontal">

    <ImageView
        android:id="@+id/gridItem"
        android:layout_
        android:layout_
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true" />

    <ImageView
        android:id="@+id/gridItemOverlay"
        android:layout_
        android:layout_
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:background="@drawable/button_ripple" />

</RelativeLayout>

CustomGridAdapter.java

....
gridItemOverLay = (ImageView) findViewById(R.id.gridItemOverlay);
gridItemOverlay.bringToFront();

这行得通。现在底部 ImageView 包含我的图像,顶部动画,给人一种涟漪动画的错觉在我的图像顶部。老实说,尽管这是一种解决方法。我觉得这不是它的意图。所以我问你们好人,有没有更好的方法,甚至是不同的方法?

【问题讨论】:

请为 button_ripple 可绘制对象添加 XML。 @alanv 按要求添加 元素不是选择器,而是层列表。查看 RippleDrawable 的文档。 @alanv 我阅读了文档,但我仍然有点困惑。在自定义适配器或多个资源的情况下该做什么从来都不是很清楚。在这种情况下,我使用 Android/obb 中的扩展文件夹中的位图填充我的网格。我最好的猜测是我使用 RippleDrawable 构造函数并每次手动/以编程方式应用它,这是正确的吗? 我对画廊项目使用了相同的方式。它有效,但波纹总是从项目的中间开始。你有同样的问题或解决办法吗? 【参考方案1】:

我喜欢 android 开发人员的回答,所以我决定研究如何在代码中执行他的解决方案的第 2 步。

您需要从 Jake Wharton 获取这段代码:https://gist.github.com/JakeWharton/0a251d67649305d84e8a

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;


public class ForegroundImageView extends ImageView 
  private Drawable foreground;


  public ForegroundImageView(Context context) 
    this(context, null);
   


  public ForegroundImageView(Context context, AttributeSet attrs) 
    super(context, attrs);


    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundImageView);
    Drawable foreground = a.getDrawable(R.styleable.ForegroundImageView_android_foreground);
    if (foreground != null) 
      setForeground(foreground);
     
    a.recycle();
   


  /** 
   * Supply a drawable resource that is to be rendered on top of all of the child 
   * views in the frame layout. 
   * 
   * @param drawableResId The drawable resource to be drawn on top of the children. 
   */ 
  public void setForegroundResource(int drawableResId) 
    setForeground(getContext().getResources().getDrawable(drawableResId));
   


  /** 
   * Supply a Drawable that is to be rendered on top of all of the child 
   * views in the frame layout. 
   * 
   * @param drawable The Drawable to be drawn on top of the children. 
   */ 
  public void setForeground(Drawable drawable) 
    if (foreground == drawable) 
      return; 
     
    if (foreground != null) 
      foreground.setCallback(null);
      unscheduleDrawable(foreground);
     


    foreground = drawable;


    if (drawable != null) 
      drawable.setCallback(this);
      if (drawable.isStateful()) 
        drawable.setState(getDrawableState());
       
     
    requestLayout();
    invalidate();
   


  @Override protected boolean verifyDrawable(Drawable who) 
    return super.verifyDrawable(who) || who == foreground;
   


  @Override public void jumpDrawablesToCurrentState()  
    super.jumpDrawablesToCurrentState(); 
    if (foreground != null) foreground.jumpToCurrentState();
   


  @Override protected void drawableStateChanged()  
    super.drawableStateChanged(); 
    if (foreground != null && foreground.isStateful()) 
      foreground.setState(getDrawableState());
     
   


  @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (foreground != null) 
      foreground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
      invalidate();
     
   


  @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) 
    super.onSizeChanged(w, h, oldw, oldh);
    if (foreground != null) 
      foreground.setBounds(0, 0, w, h);
      invalidate();
     
   


  @Override public void draw(Canvas canvas) 
    super.draw(canvas);


    if (foreground != null) 
      foreground.draw(canvas);
     
   
 

这是 attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="ForegroundImageView">
    <attr name="android:foreground"/>
  </declare-styleable>
</resources>

现在,像这样在 layout.xml 中创建 ForegroundImageView:

<com.example.ripples.ForegroundImageView
    android:layout_
    android:layout_
    android:scaleType="centerCrop"
    android:foreground="?android:selectableItemBackground"
    android:src="@drawable/apples"
    android:id="@+id/image" />

图像现在会出现波纹。

【讨论】:

比将图像视图包装在执行速度较慢的布局组中要好得多【参考方案2】:

您可以像这样在 GridView 级别添加波纹,而不是尝试将波纹添加到适配器中的每个单独视图:

<GridView
    android:id="@+id/gridview"

    ...

    android:drawSelectorOnTop="true"
    android:listSelector="@drawable/your_ripple_drawable"/>

【讨论】:

【参考方案3】:

也许可以尝试其中一种解决方案(从here 得到提示):

    将drawable包装在RippleDrawable²中,然后再将其设置在ImageView上:

    Drawable image = …
    RippleDrawable rippledImage = new RippleDrawable(
        ColorStateList.valueOf(rippleColor), image, null);
    imageView.setImageDrawable(rippledImage);
    

    扩展ImageView 并为其添加一个前景属性(如FrameLayout has³)。请参阅 +Chris Banes 中的这个示例⁴,将其添加到 LinearLayout。如果您这样做,请确保您通过触摸坐标,以便波纹从正确的点开始:

    @Override
    public void drawableHotspotChanged(float x, float y) 
        super.drawableHotspotChanged(x, y);
        if (foreground != null) 
            foreground.setHotspot(x, y);
        
    
    

【讨论】:

【参考方案4】:

我有一个图片库(使用 RecyclerViewGridLayoutManager 构建)。图像是使用毕加索设置的。为了给图像添加波纹我用FrameLayout 包裹了ImageView。项目布局为:

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_
    android:foreground="@drawable/ripple"
    android:clickable="true"
    android:focusable="true"
    >
    <ImageView
        android:id="@+id/grid_item_imageView"
        android:layout_
        android:layout_
        android:layout_gravity="center"
        android:scaleType="centerInside"
        />
</FrameLayout>

注意 android:foreground。它与android:background 不同。我试过没有android:clickable="true"android:focusable="true",它也工作,但它没有伤害。

然后在res/drawable中添加一个ripple.xmldrawable:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <solid android:color="#7FF5AC8E" />
        </shape>
    </item>
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@android:color/transparent" />
        </shape>
    </item>
</selector>

请注意,当项目被选中时,这会在图像顶部显示半透明颜色(适用于

然后将带有波纹的ripple.xmldrawable 添加到res/drawable-v21

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="@color/coral">
</ripple>

您可以使用默认的涟漪效果来代替自定义的ripple.xml,但在图像顶部确实很难看到,因为它是灰色的:

android:foreground="?android:attr/selectableItemBackground"

【讨论】:

以上是关于图像顶部的波纹效果 - Android的主要内容,如果未能解决你的问题,请参考以下文章

如何给Imageview 设置水波纹效果

动态壁纸水波纹效果

Android 自定义view实现水波纹效果

在 ImageButton 之外实现波纹效果

imageview 上的波纹效果

matlab练习程序(水波特效)