Khi build 1 app mobile thì điều quan trọng trước tiên là làm sao để tạo ra một UI chính xác, đẹp, xử lý được tất cả các yêu cầu từ khách hàng hoặc designer.
Mình xin phép tổng hợp 1 vài nguyên tắc và kinh nghiệm khi thực hiện dựng layout trong Android để mục đích tham khảo và lưu trữ. Có thể có nhiều thứ là bình thường, nhưng có một số cái cũng research khá nhiều mới tìm thấy :)
CÁC NGUYÊN TẮC CƠ BẢN
KHI NÀO DÙNG MARGIN? KHI NÀO DÙNG PADDING?
Có thể khi đề cập đến vấn đề này, bạn sẽ nghĩ rằng ai cũng biết. Nhưng không hẳn, rất nhiều người vẫn nhầm lẫn hoặc sử dụng qua loa 2 thuộc tính này trên layout. Việc sử dụng không đúng trong 1 vài trường hợp có thể cho ra kết quả mong muốn, nhưng không thể hiện bạn hiểu đầy đủ lĩnh vực mà bạn đang làm.
- Như đã nói trong 1 số trường hợp đơn giản, margin và padding cho ra kết quả giống nhau. Và ta mặc nhiên xem như đã hoàn thành task đó, trong khi nó vẫn tiềm ẩn sai sót khi nâng cấp UI về sau.
Layout Margin: Khi ta khai báo thuộc tính layout_margin (canh lề) cho một thành phần nào đó, thì nó sẽ tạo ra một khoảng cách giữa thành phần đó với các thành phần xung quanh nó (top, right, bottom và left). Tạo ra sự phân chia giữa các thành phần trong trang layout được thể hiện rõ ràng hơn.
Padding: là phần nằm giữa phần hiển thị nội dung và đường viền (border). Khi một thành phần được khai báo padding thì sẽ tạo ra một khoảng trống với dường viền giúp nội dung dễ nhìn hơn.
SỬ DỤNG ĐỊNH NGHĨA STYLE NHIỀU NHẤT CÓ THỂ
Khi thiết kế layout trong Android, ta thường chỉ chú ý đến các thuộc tính của đối tượng trong layout mà quên mất rằng Android có 1 phần định nghĩa styles.xml cho việc kế thừa và điều chỉnh layout tốt hơn. Chúng ta thường cố gắng chỉnh các thuộc tính sao cho UI trở nên hợp lý mà quên mất việc tìm các thành phần chung giữa các layout và tạo thành các style.
Vì sao nên sử dụng style trong android?
- Đầu tiên, bạn sẽ thống nhất được các thuộc tính của những thành phần chung giữa các layout, cố gắng định nghĩa nó 1 lần và chỉ cần gọi lại mà không cần lặp lại việc tương tự trong các layout khác, mất thời gian.
- Khi cần thay đổi 1 thành phần thuộc tính nào đó, như màu sắc, font sỉz, background... chỉ cần thay đổi style tương ứng cho tất cả các layout được sử dụng.
- Khi có nhiều thành phần gần tương đồng về style. Ta có thể tạo ra các base style và sau đó định nghĩa các style con kế thừa "parent" các base style này. Rất nhanh, không cần lặp lại các thuộc tính tương đồng.
- Dễ dàng thay đổi hay xóa bỏ các thành phần tương đồng giữa các layout, ta chỉ việc thay đổi hoặc xóa style (xóa style thì IDE sẽ báo lỗi cho tất cả các layout có gọi nó, đảm bảo không bao giờ sót layout).
CUSTOM DRAWABLE BẰNG VIỆC SỬ DỤNG HÌNH ẢNH NHƯ THẾ NÀO LÀ HỢP LÝ?
Khi sử dụng custom drawable, ta có 2 cách là xử sụng xml để define chúng trong folder drawable hoặc sử dụng image resource (hay còn gọi là slicing images) để tạo ra drawable.
Thông thường chúng ta khuyến khích cố gắng sử dụng cách làm đầu tiên với bất cứ design layout nào có thể từ designer. Sử dụng cơ chế drawable xml render của chính android cho phép giảm kích thước ứng dụng vì không phải mang vác theo cả mớ ảnh resource như cách 2 và cũng tăng khả năng responsive với nhiều loại devices.
Vậy, nếu design quá phức tạp, như các hiệu ứng design trên button... thì ta phải sử dụng image resource để custom. Vấn đề là ảnh thì sẽ fix kích thước và không designer nào có thể xử lý nổi từng design cho từng loại device. Vậy làm sao để xử lý responsive khi sử dụng image resource trong android. Câu trả lời là sử dụng Custom Nine-Patch.
Một drawable Nine-patches là một dạng đặc biệt của hình ảnh đó có thể được thu nhỏ theo chiều rộng và chiều cao trong khi duy trì tính toàn vẹn hình ảnh của mình. Nine-patches vá lỗi là cách phổ biến nhất để xác định sự xuất hiện của các nút Android, mặc dù bất kỳ loại drawable có thể được sử dụng.
Cách thiết kế nine-patches bạn có thể tìm thấy các link hướng dẫn cụ thể trên google. Nhớ răng, mặc định Android SDK có cung cấp 1 tool xử lý vấn đề này xuất ra các file *.9.png cho bạn tại: android-sdk\tools\draw9patch.bat
SỬ DỤNG HỆ THỐNG ĐƠN VỊ ĐO TRONG ANDROID NHƯ THẾ NÀO?
Cái này cũng là một lưu ý nhỏ mà quan trọng khi thiết kế responsive layout tốt hơn.
1. Pixel (px): có khi gọi là pel (xuất phát từ “picture element”), chúng ta hay gọi là điểm ảnh, có dạng hình vuông.
2. dp, hay dip hay Density-independent Pixels: có khi được gọi là Device-independent Pixels. Đây là một đơn vị đo chiều dài vật lý cũng giống như inch, cm, mm… mà Google thường áp dụng để đo kích thước màn hình của thiết bị.
3. DPI – Dots per inch hay PPI – Pixels per inch: số điểm ảnh (pixels) trên 1 inch của màn hình thiết bị, con số này càng lớn thì màn hình thiết bị hiển thị hình ảnh càng mịn và đẹp.
Dựa vào dpi người ta chia làm loại màn hình như sau:
– small: ldpi (120dpi)
– normal: mdpi (160dpi)
– large: hdpi (240dpi)
– x-large: xhdpi (320dpi).
Với mỗi loại này thì 1 dp tương ứng với số lượng pixels khác nhau, được tính theo công thức:
px = dp * (dpi / 160)
Ví dụ với thiết bị có dpi là 320 thì với 10 dp ta có: 10 * (320/160) = 20 px, 1 dp tương đương 2 px.
4. PT – Point: khái niệm pt tương tự như dp là một đơn vị đo kích thước thực, nhưng khác với dp:
6. Cách chuyển đổi giữa dp và px hay dùng trong android code khi cần xử lý responsive hay điều chỉnh kích thước view.
public static float convertDpToPixels(float dp,Context context){ Resources resources = context.getResources(); DisplayMetrics metrics = resources.getDisplayMetrics(); float px = dp * (metrics.densityDpi/160f); return px; } public static float convertPixelsToDp(float px,Context context){ Resources resources = context.getResources(); DisplayMetrics metrics = resources.getDisplayMetrics(); float dp = px / (metrics.densityDpi / 160f); return dp; }
MỘT SỐ VẤN ĐỀ VÀ KINH NGHIỆM GIẢI QUYẾT
TẠO AUTOSCROLL TEXTVIEW KHI NỘI DUNG CỦA NÓ VƯỢT QUÁ CHIỀU NGANG
Khi nội dung của TextView (trên titlebar chẳng hạn) vượt quá chiều ngang của nó thì ta có thể tạo hiệu ứng auto horizontal scroll để người dễ dàng nắm bắt được nội dung của nó. Đây cũng là 1 trong những cách để xử lý responsive với nhiều loại thiết bị trong andorid.
Giải quyết: Thêm các thuộc tính cho TextView vào layout như bên dưới
<TextView android:id="@+id/tv_titlebar" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:ellipsize="marquee" android:fadingEdge="horizontal" android:marqueeRepeatLimit="marquee_forever" android:singleLine="true" />
Set selected cho nó trong code khi init nội dung của TextView
tvTitle.setText("DEMO DEMO DEMO DEMO DEMO DEMO DEMO DEMO"); tvTitle.setSelected(true);
TỰ ĐỘNG THÊM DẤU ... VÀO SAU NỘI DUNG TEXTVIEW KHI NỘI DUNG VƯỢT QUÁ ĐỘ DÀI
Như trường hợp trên, tuy nhiên thường áp dụng với các dòng content không quan trọng và có action để xem chi tiết hơn trong các view tiếp theo.
Giải quyết: Thêm các thuộc tính cho TextView vào layout như bên dưới
<TextView android:id="@+id/tv_titlebar" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:ellipsize="end" android:singleLine="true" />
TẠO HIỆU ỨNG FOREGROUND SELECTOR CHO LISTITEM
Yêu cầu đơn giản là tạo hiệu ứng selector như hình bên dưới. Rất ngắn gọn, nhưng làm sao?
Giải quyết: Sau khi đã set custom drawable selector cho ListView thì set thêm thuộc tính android:drawSelectorOnTop=true vào.
XML mẫu như thế này:
<ListView android:id="@+id/lv_notification" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="@color/line_list_divider" android:dividerHeight="1dp" android:background="@color/bg_list_color" android:drawSelectorOnTop="true" android:listSelector="@drawable/list_item_home" />
LISTVIEW TỰ ĐỘNG GIÃN CHIỀU CAO THEO CHÍNH CÁC LISTITEM BÊN TRONG NÓ
Khi sử dụng ListView, chúng ta thường set chiều cao của nó là match_parent. Tuy nhiên, trong vài trường hợp ta cần set chiều cao của nó vừa đúng tổng chiều cao của các item của nó. Vậy làm sao?
Giải quyết: Cách tốt nhất là tạo 1 class custom ListView và override lại phần tính toán chiều cao của ListView khi render. Khi render, nó sẽ tự get Adapter và tính toán chiều cao các item bên trong + phần padding của chính ListView. Như thế là xong.
public class WrapContentListView extends ListView { ... @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); setHeightWrapContent(); } public void setHeightWrapContent() { ListAdapter adapter = getAdapter(); if (adapter == null) { return; } int totalHeight = getPaddingTop() + getPaddingBottom(); ; for (int i = 0; i < adapter.getCount(); i++) { View listItem = adapter.getView(i, null, this); listItem.measure(0, 0); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = this.getLayoutParams(); params.height = totalHeight + (this.getDividerHeight() * (adapter.getCount() - 1)); this.setLayoutParams(params); } }
<customview_package.WrapContentListView android:id="@+id/lv_extservice" android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none" />
GRIDVIEW TỰ ĐỘNG GIÃN CHIỀU CAO THEO CHÍNH CÁC LISTITEM BÊN TRONG NÓ
Bối cảnh thì cũng tương tự như ListView phía trên thôi. Tuy, điểm khác biệt quan trọng là GridView có số lượng cột và dòng, chứ k chỉ là dòng như ListView.
Giải quyết: Như ListView, ta custom lại GridView và sử dụng nó trong layout.
public class WrapContentGridView extends GridView { ... @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); setHeightWrapContent(); } public void setHeightWrapContent() { int totalHeight = Utils.getGridViewHeightBasedOnChildren(this, getNumColumns(), -1); ViewGroup.LayoutParams params = this.getLayoutParams(); params.height = totalHeight; this.setLayoutParams(params); } private int getGridViewHeightBasedOnChildren(GridView gridView, int numColumn) { int totalHeight = gridView.getPaddingTop() + gridView.getPaddingBottom(); ListAdapter adapter = gridView.getAdapter(); if (adapter != null) { int numRow = (int) Math.ceil(adapter.getCount() * 1.0 / numColumns); for (int i = 0; i < numRow; i++) { View listItem = adapter.getView(i, null, gridView); listItem.measure(0, 0); totalHeight += listItem.getMeasuredHeight(); if (i != 0) { totalHeight += gridView.getVerticalSpacing(); } } } return totalHeight; } }
TEXTVIEW CÓ STYLE GẠCH CHÂN
Yêu cầu là hiển thị đoạn TextView trên layout có gạch chân.
Giải quyết: Có nhiều cách xử lý trong trường hợp này: xử lý nội dung trong onCreate của layout,... Tuy nhiên, cách đơn giản và nhanh nhất vẫn là custom 1 cái TextView có underline và khi cần chỉ gọi nó ra từ layout xml.
public class UnderlineTextView extends BaseCustomTextView { protected boolean m_modifyingText = false; ... public UnderlineTextView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { //Do nothing here... we don't care } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { //Do nothing here... we don't care } @Override public void afterTextChanged(Editable s) { applySpannable(); } }); applySpannable(); } private SpannableString getSpannableString() { SpannableString finalText = new SpannableString(getText().toString()); finalText.setSpan(new UnderlineSpan(), 0, finalText.length(), 0); return finalText; } private void applySpannable() { if (m_modifyingText) return; m_modifyingText = true; super.setText(getSpannableString(), BufferType.SPANNABLE); m_modifyingText = false; } }
IMAGEVIEW VUÔNG
Khi sử dụng ImageView để hiển thị icon của 1 số item, yêu cầu chung thường set nó là hình vuông theo đúng tỉ lệ. Nhiều bạn thường nghĩ là cứ đưa ImageView vào bình thường rồi lên code xử lý xử lý lại chiều rộng và chiều cao. Cũng ok, nhưng không phải là tốt nhất.
Giải quyết: Custom lại ImageView như bên dưới (cho trường hợp kích thước vuông phụ thuộc theo chiều cao của ImageView, ngược lại tương tự).
Lưu ý, việc custom vuông này có thể áp dụng cho bất kỳ view nào, chỉ cần extends lại và gọi customview từ layout.
public class SquareVerticalImageView extends ImageView { ... @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(heightMeasureSpec, heightMeasureSpec); } }
IMAGEVIEW CÓ KÍCH THƯỚC TỈ LỆ THEO DRAWABLE CHỨA TRONG NÓ
Khi cần show các hình ảnh trong layout với yêu cầu show hết theo chiều rộng và giữ nguyên tỉ lệ ảnh thì phải làm sao. Đứng nói là lại lên code xử lý set layoutparams nhé :v
Giải quyết: Custom lại ImageView như bên dưới cho trường hợp giãn kích thước tối đa chiều ngang mà vẫn giữ tỉ lệ Drawable trong nó.
Lưu ý, việc custom vuông này có thể áp dụng cho bất kỳ view nào có chưa Drawable resource như ImageView, ImageButton..., chỉ cần extends lại và gọi customview từ layout.
public class AspectRatioHorizontalImageView extends ImageView { ... @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (getDrawable() != null) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = width * getDrawable().getIntrinsicHeight() / getDrawable().getIntrinsicWidth(); setMeasuredDimension(width, height); } else { setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } } }
DISABLE SỰ KIỆN TOUCH VÀO VIEW (KHI LỒNG VIEW DẠNG CHỈ HIỂN THỊ) VÀO 1 VIEW TOUCHABLE
Khi lồng 1 view touchable vào 1 view touchable khác, mặc định Android sẽ bắt sự kiện touch ở cả 2 view. Tuy nhiên, trong 1 số trường hợp ta chỉ xem view lồng thêm vào là view chỉ hiển thị mà không quan tâm đến action của nó, nhưng nó vẫn chiếm touch của view cha. Đây là 1 issue khá khó chịu nhưng không hẳn nhiều người biết cách giải quyết nhanh nhất.
Giải quyết: Rất đơn giản, custom lại cái view cần lồng vào và xử lý override như sau
public class NotTouchableView extends View { ... @Override public boolean onTouchEvent(MotionEvent ev) { return false; } }
FILL CONTENT TRONG SCROLLVIEW
Khi sử dụng ScrollView, ta thường phải lồng vào trong đó 1 LinearLayout có thuộc tính layout_height = WRAP_CONTENT để ScrollView nhận được phần độ cao của nội dung bên trong nó. Có 1 vấn đề, nếu ta set custom background cho LinearLayout bên trong thì khi content của LinearLayout chưa vượt quá độ cao của ScrollView, nó sẽ chỉ hiển thị đúng phần background tương xứng. Với điều này, trong vài trường hợp sẽ làm xấu app.
Giải quyết: Thêm thuộc tính fillViewport="true" vào ScrollView, content của LinearLayout sẽ tự fill hết độ cao của ScrollView.
<ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"> ... </LinearLayout> </ScrollView>
GOOGLE MAP (HOẶC 1 VIEW NÀO KHÁC CÓ TOUCH EVENT) KHÔNG THỂ BẮT CHÍNH XÁC SỰ KIỆN TOUCH, DRAG KHI LỒNG VÀO TRONG 1 SCROLLVIEW
Map là 1 control rất hay sử dụng, nhưng chúng ta thường chỉ hay nhúng nó vào activity hay fragment cố định. Vậy khi được yêu cầu nhúng vào 1 ScrollViewer thì làm sao. Ok, cứ nhúng vào trong LinearLayout của ScrollView thôi, nhưng lúc đó, các gesture của map sẽ không hoạt động được nữa vì nó sẽ bị ScrollView chiếm.
Giải quyết: Thực hiện custom lại ScrollView như sau
public class CustomScrollView extends ScrollView { List<View> mInterceptScrollViews = new ArrayList<View>(); ... public void addInterceptScrollView(View view) { mInterceptScrollViews.add(view); } public void removeInterceptScrollView(View view) { mInterceptScrollViews.remove(view); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { // check if we have any views that should use their own scrolling if (mInterceptScrollViews.size() > 0) { int x = (int) event.getX(); int y = (int) event.getY(); Rect bounds = new Rect(); for (View view : mInterceptScrollViews) { view.getHitRect(bounds); if (bounds.contains(x, y)) { //were touching a v that should intercept scrolling return false; } } } return super.onInterceptTouchEvent(event); } }
Khi init layout view trong code, ta sẽ add các view mà ScrollView sẽ ưu tiên Touch Event cho chúng
scrollView.addInterceptScrollView(mapView);
Lưu ý: Google Map chỉ là 1 ví dụ điển hình. Bạn có thể sử dụng cách này cho các view con khi muốn ưu tiên Touch Event cho chúng.
NGĂN SCROLLVIEW KHÔNG TỰ ĐỘNG SCROLL ĐẾN 1 VIEW KHI NHÚNG SCROLLVIEW VÀO VIEWPAGER
Tình huống khá ít gặp, nhưng làm nhiều rồi cũng gặp thôi. Vấn đề như sau:
- Ta có 1 viewpager chứa các fragment.
- Trong các fragment ta có nhúng các ScrollView để cuộn nội dung bên trong nó
- Khi swipe giữa các trang của viewpager thì đôi lúc ScrollView tự động scroll tới 1 view khác trong content mà không rõ vì sao :v
Đây không hẳn là bug, đây là 1 tính năng :D nhưng ta không thích
Giải quyết: Custom các ScrollView đó thành như sau là ok
public class NotAutoScrollingScrollView extends ScrollView { ... @Override public void requestChildFocus(View child, View focused) { if (focused instanceof ListView) return; super.requestChildFocus(child, focused); } }
SET BACKGROUND CHO LAYOUT DÃN ĐÚNG TỈ LỆ ẢNH RESOURCE VÀ ƯU TIÊN CONTENT ẢNH THEO 1 CHIỀU NÀO ĐÓ MÀ KHÔNG PHẢI LÀ CENTER NỘI DUNG
Hơi khó hiểu 1 chút, đại khái là yêu cầu muốn set 1 background cho layout là 1 cái ảnh. Có điều ng ta muốn lấy ưu tiên nội dung background theo phía dưới ảnh (tức là phần trên của ảnh có thể mất không quan trọng nhưng phần dưới cùng là bắt buộc :v). Thêm 1 yêu cầu nữa là phần ảnh background này phải keep tỉ lệ của ảnh, không được stretch làm méo hình ảnh.
- Nếu chỉ đơn thuần là set background đến file resource đó thì hình ảnh sẽ bị méo.
- Nếu sử dụng ImageView lồng vào bên dưới FrameLayout để làm background:
+ Nếu set thuộc tính scaleType là center (bất kỳ center nào) thì sẽ đảm bảo ratio nhưng k đảm bảo ưu tiên phần content bottom
+ Nếu set thuộc scaleType là fit (start hoặc end, XY chắc chắn không được vì vỡ ratio) thì không đảm bảo được dãn theo hết chiều ngang của layout nền.
Giải quyết:
- Đầu tiên sử dụng custom AspectRatioHorizontalImageView để ImageView tự động dãn kích thước theo chiều ngang mà vẫn đảm bảo tỉ lệ của Drawable resource.
- Lồng ImageView đó vào FrameLayout với thuộc tính layout_gravity là bottom (hoặc top với trường hợp ưu tiên phía trên).
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <biz.vipliner.view.AspectRatioHorizontalImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:src="@drawable/bg_home" /> <LinearLayout android:id="@+id/ln_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> ... </LinearLayout> </FrameLayout>
Như vây, ta đã có kết quả như mong muốn:
THỰC HIỆN HIỆU ỨNG ĐỔ BÓNG (SHADOW) TRONG KHI CUSTOM DRAWABLE XML
Android hỗ trợ cho chúng ta có thể custom Drawable bằng xml như layout với các đối tượng như selector, item, layer-list, shape, solid, corners, gradient... Rất phong phú, tùy khả năng phối hợp mà sẽ tái tạo được hầu hết các hiệu ứng trong design của designer. Hạn chế sử dụng image resource.
Tuy nhiên, 1 thiếu sót cho đối với các SDK <5.0 là không hỗ trợ hiệu ứng đổ bóng trong khi thiết kế drawable. Vậy phải làm sao khi vẫn không muốn sử dụng drawable nine-patches?
Giải quyết: Tất nhiên nếu đã không hỗ trợ thì phải tìm cách tái tạo gần tương đường hoặc đôi khi giống hệt :D
- Nguyên tắc cơ bản là hay vẽ lại cái hình tròn phía trên bằng 1 màu xám nhạt (giả màu shadow) dưới cùng trước tiên. Cho lệch tối đa về phía dưới.
- Chồng tiếp 1 hoặc 2 hình tròn xám nhạt nữa và lệch lần lượt 1dp, 2dp lên dần phía trên.
- Sau cùng vẽ hình tròn trắng chồng đúng margin 3dp, hoặc 2dp.
- Có thể chồng tiếp theo các màu gradient khác nếu design phức tạp hơn nữa.
XML mẫu như sau:
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <!-- shadow--> <item android:bottom="0dp" android:left="1dp" android:right="1dp" android:top="2dp"> <shape android:shape="oval"> <solid android:color="#11000000" /> </shape> </item> <item android:bottom="1dp" android:left="2dp" android:right="2dp" android:top="2dp"> <shape android:shape="oval"> <solid android:color="#19000000" /> </shape> </item> <!-- backbround--> <item android:bottom="2dp" android:left="2dp" android:right="2dp" android:top="2dp"> <shape android:shape="oval"> <solid android:color="@color/white" /> </shape> </item> <item android:bottom="4dp" android:left="4dp" android:right="4dp" android:top="4dp"> <shape android:shape="oval"> <gradient android:angle="90" android:endColor="@color/white" android:startColor="#E2F8FF" /> </shape> </item> </layer-list>
Leave a Comment