2011-11-07 40 views
16

Ứng dụng tôi hiện đang làm việc trên sử dụng rất nhiều ImageViews làm nút. Đồ họa trên các nút này sử dụng kênh alpha để làm mờ các cạnh của nút và khiến chúng trông không đều. Hiện tại, chúng ta phải tạo 2 đồ họa cho mỗi nút (1 cho trạng thái được chọn/tập trung/nhấn và cái kia cho trạng thái không được chọn mặc định) và sử dụng một StateListDrawable được định nghĩa trong một tệp XML cho mỗi nút.Màu/mờ có thể vẽ trên màn hình

Trong khi điều này hoạt động tốt có vẻ như rất lãng phí vì tất cả các đồ họa được chọn chỉ đơn giản là phiên bản màu của các nút không được chọn. Những thời gian này để sản xuất (tuy nhiên ít) và chiếm không gian trong APK cuối cùng. Có vẻ như có một cách dễ dàng để điều này tự động.

Giải pháp hoàn hảo, dường như, là sử dụng ImageViews cho mỗi nút và chỉ định trong thuộc tính màu của nó là một ColorStateList. Cách tiếp cận này có lợi thế là chỉ một XML ColorStateList đơn là cần thiết cho tất cả các nút (chia sẻ cùng một màu). Tuy nhiên, nó không hoạt động. Như đã đề cập here, ImageView ném một NumberFormatException khi giá trị được cung cấp cho màu là bất kỳ thứ gì ngoài một màu.

Nỗ lực tiếp theo của tôi là sử dụng LayerDrawable cho đối tượng được chọn. Bên trong danh sách lớp, chúng tôi sẽ có hình ảnh gốc ở dưới cùng của ngăn xếp được bao phủ bởi một hình chữ nhật bán trong suốt. Điều này làm việc trên các phần rắn của đồ họa nút. Tuy nhiên, các cạnh được cho là hoàn toàn trong suốt, bây giờ có cùng màu với lớp trên cùng.

Có ai gặp sự cố này trước đây và tìm thấy giải pháp hợp lý không? Tôi muốn gắn bó với các cách tiếp cận XML nhưng có lẽ sẽ mã hóa một lớp con ImageView đơn giản sẽ thực hiện việc nhuộm màu theo yêu cầu trong mã.

Trả lời

6

Bạn có thể kết hợp StateListDrawableLayerDrawable cho việc này.

public Drawable getDimmedDrawable(Drawable drawable) { 
     Resources resources = getContext().getResources(); 
     StateListDrawable stateListDrawable = new StateListDrawable(); 
     LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{ 
       drawable, 
       new ColorDrawable(resources.getColor(R.color.translucent_black)) 
     }); 
     stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, layerDrawable); 
     stateListDrawable.addState(new int[]{android.R.attr.state_focused}, layerDrawable); 
     stateListDrawable.addState(new int[]{android.R.attr.state_selected}, layerDrawable); 
     stateListDrawable.addState(new int[]{}, drawable); 
     return stateListDrawable; 
} 

Tôi giả sử màu sắc của mình.xml trông như thế này

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
    <color name="translucent_black">#80000000</color> 
</resources> 
+0

Làm cách nào để nó hoạt động tốt cho các tệp 9-patch, sao cho toàn bộ khu vực không trong suốt của hình ảnh sẽ bị tối khi các sự kiện cảm ứng? Hiện tại những gì nó làm cho hình ảnh 9-vá là chỉ thay đổi phần được định nghĩa là nội dung của hình ảnh, chứ không phải toàn bộ hình ảnh ... –

17

Đối với những người đã gặp phải một nhu cầu tương tự, giải quyết điều này trong mã là khá sạch sẽ. Đây là một mẫu:

public class TintableButton extends ImageView { 

    private boolean mIsSelected; 

    public TintableButton(Context context) { 
     super(context); 
     init(); 
    } 

    public TintableButton(Context context, AttributeSet attrs) { 
     super(context, attrs); 
     init(); 
    } 

    public TintableButton(Context context, AttributeSet attrs, int defStyle) { 
     super(context, attrs, defStyle); 
     init(); 
    } 

    private void init() { 
     mIsSelected = false; 
    } 

    @Override 
    public boolean onTouchEvent(MotionEvent event) { 
     if (event.getAction() == MotionEvent.ACTION_DOWN && !mIsSelected) { 
      setColorFilter(0x99000000); 
      mIsSelected = true; 
     } else if (event.getAction() == MotionEvent.ACTION_UP && mIsSelected) { 
      setColorFilter(Color.TRANSPARENT); 
      mIsSelected = false; 
     } 

     return super.onTouchEvent(event); 
    } 
} 

Nó chưa hoàn thành nhưng hoạt động tốt như một bằng chứng về khái niệm.

+0

Đây là IMHO, giải pháp tốt nhất và phải là câu trả lời được chấp nhận, vì nó hoạt động tốt và tạo thành phần có thể sử dụng lại trong bất kỳ bố cục nào để thay thế một ImageView. Giải pháp tốt! – GreyBeardedGeek

+1

Cảm ơn, rất tuyệt! Tôi đã sửa đổi onTouchEvent một chút như thế này: 'else if ((event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) && mIsSelected)' Điều này đảm bảo rằng màu sắc biến mất khi người dùng giữ nút, hãy kéo ngón tay ra ngoài và nhả ra. – Jaykob

+0

'init()' chỉ nên được gọi trong 'TintableButton (Ngữ cảnh bối cảnh, AttributeSet attrs, int defStyle)' constructor –

6

Your answer was excellent :)

Tuy nhiên thay vì tạo một đối tượng nút, tôi chỉ gọi một OnTouchlistener thay đổi trạng thái của tất cả các quan điểm.

public boolean onTouch(View v, MotionEvent event) { 
    if (event.getAction() == MotionEvent.ACTION_DOWN) { 
     Drawable background = v.getBackground(); 
     background.setColorFilter(0xBB000000, PorterDuff.Mode.SCREEN); 
     v.setBackgroundDrawable(background); 
    } else if (event.getAction() == MotionEvent.ACTION_UP) { 
     Drawable background = v.getBackground(); 
     background.setColorFilter(null); 
     v.setBackgroundDrawable(background); 
    } 

    return true; 

} 

Cung cấp các kết quả tương tự mặc dù

+2

Điều đó cũng hoạt động. Lý do tôi chọn để mở rộng các lớp cần thiết là để tôi có thể xử lý tất cả điều này trong một XML bố cục sau khi lớp đã sẵn sàng. Bằng cách đó, mã sử dụng các tiện ích có thể tô màu này không khác với mã sử dụng các thành phần tiêu chuẩn. – zienkikk

1

tôi cũng có các nút tìm không thường xuyên và cần các màu. Cuối cùng, tôi chỉ sử dụng danh sách màu của tiểu bang làm nền ImageButton của mình. Tạo ấn tượng rằng màu của nút đang thay đổi trên báo chí và rất đơn giản và ít tính toán chuyên sâu hơn. Tôi biết rằng đây không phải là một màu sắc chính xác, nhưng nó cho nó cung cấp phản hồi trực quan cần thiết cho người dùng trong những gì thường là một khoảnh khắc thoáng qua.

<?xml version="1.0" encoding="utf-8"?> 
<selector xmlns:android="http://schemas.android.com/apk/res/android" > 
    <item 
     android:state_pressed="true" 
     android:drawable="@android:color/darker_gray" /> 
    <item android:drawable="@android:color/transparent" /> 
</selector> 
1

Your answer cũng là tuyệt vời;)

Tôi đang thay đổi một chút, nó sẽ cung cấp cho bạn kết quả như thế này:

button_normalbutton_pressed

Rect rect; 
Drawable background; 
boolean hasTouchEventCompleted = false; 

@Override 
public boolean onTouch(View v, MotionEvent event) { 
    if (event.getAction() == MotionEvent.ACTION_DOWN) { 
     rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), 
       v.getBottom()); 
     hasTouchEventCompleted = false; 
     background = v.getBackground(); 
     background.setColorFilter(0x99c7c7c7, 
       android.graphics.PorterDuff.Mode.MULTIPLY); 
     v.setBackgroundDrawable(background); 
    } else if (event.getAction() == MotionEvent.ACTION_UP 
      && !hasTouchEventCompleted) { 
     background.setColorFilter(null); 
     v.setBackgroundDrawable(background); 

    } else if (event.getAction() == MotionEvent.ACTION_MOVE) { 
     if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() 
       + (int) event.getY())) { 
      // User moved outside bounds 
      background.setColorFilter(null); 
      v.setBackgroundDrawable(background); 
      hasTouchEventCompleted = true; 
     } 
    } 


    //Must return false, otherwise you need to handle Click events yourself. 
    return false; 
} 
0

Tôi nghĩ giải pháp này đủ đơn giản:

final Drawable drawable = ... ; 
    final int darkenValue = 0x3C000000; 
    mButton.setOnTouchListener(new OnTouchListener() { 
     Rect rect; 
     boolean hasColorFilter = false; 

     @Override 
     public boolean onTouch(final View v, final MotionEvent motionEvent) { 
      switch (motionEvent.getAction()) { 
       case MotionEvent.ACTION_DOWN: 
        rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 
        drawable.setColorFilter(darkenValue, Mode.DARKEN); 
        hasColorFilter = true; 
        break; 
       case MotionEvent.ACTION_MOVE: 
        if (rect.contains(v.getLeft() + (int) motionEvent.getX(), v.getTop() 
          + (int) motionEvent.getY())) { 
         if (!hasColorFilter) 
          drawable.setColorFilter(darkenValue, Mode.DARKEN); 
         hasColorFilter = true; 
        } else { 
         drawable.setColorFilter(null); 
         hasColorFilter = false; 
        } 
        break; 
       case MotionEvent.ACTION_UP: 
       case MotionEvent.ACTION_CANCEL: 
        drawable.setColorFilter(null); 
        hasColorFilter = false; 
        break; 
      } 
      return false; 
     } 
    }); 
2

Tôi nghĩ giải pháp này rất đơn giản!

Bước 1: Tạo res/drawable/dim_image_view.xml

<?xml version="1.0" encoding="utf-8"?> 
<selector xmlns:android="http://schemas.android.com/apk/res/android"> 
    <item android:state_focused="true" android:drawable="@drawable/dim_image_view_normal"/> <!-- focused --> 
    <item android:state_pressed="true" android:drawable="@drawable/dim_image_view_pressed"/> <!-- pressed --> 
    <item android:drawable="@drawable/dim_image_view_normal"/> <!--default --> 
</selector> 

Bước 2: Tạo res/drawable/dim_image_view_normal.xml

<?xml version="1.0" encoding="utf-8"?> 
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> 
    <item><bitmap android:src="@mipmap/your_icon"/></item> 
</layer-list> 

Bước 3: Tạo res/drawable/dim_image_view_pressed. xml

<?xml version="1.0" encoding="utf-8"?> 
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > 
    <item><bitmap android:src="@mipmap/your_icon" android:alpha="0.5"></bitmap></item> 
</layer-list> 

Bước 4: Thử đặt hình ảnh trong bố cục xml là:

android:src="@drawable/dim_image_view" 
Các vấn đề liên quan