編輯:關於Android編程
現在購物類的APP真的是數不甚數啊,經常可以在這些APP中看到優惠券的影子,今天我們就來實現一下優惠券的背景效果。實際開發中,如果我們想偷懶,直接用一張背景圖作為優惠劵背景就OK了,今天我們手動來實現一下,其實實現起來還是比較簡單的。效果圖如下:

邊緣的樣式可以自由定制,有兩種邊緣類型:半圓形和三角形。上面圖中第一張左右兩邊邊緣為三角形、上下邊緣為半圓形,第二張左右兩本是半圓形、上下兩邊是三角形,第三張上下、左右兩邊都是半圓形。邊緣的設置都是通過在xml布局中自定義屬性指定,因此可根據需要自由定制,也可以設置為none,表示不繪制邊緣,默認類型就是none。
要實現這個自定義View,我們需要考慮的問題有三個:
(1)是繼承View還是ViewGroup?
(2)邊緣怎麼來處理?
(3)繪制的時機?
對於第一個問題,我們簡單想一下就可以得出答案,因為我們在這個自定義View中需要放入一些其他的控件,如上圖中的TextView和ImageView,如果繼承View那裡面就不能再放入其他控件了,因此我們得選擇繼承ViewGroup,但是如果我們直接是繼承ViewGroup的話,那裡面的控件擺放邏輯即onLayout還得我們自己來實現,此時就會比較復雜了,為了簡單起見,我們直接繼承自LinearLayout即可,不用去考慮onMeasure和onLayout的實現,只需關注我們這一次的業務核心即繪制不同的邊緣。
對於第二個問題,我們觀察一下上圖,只需要在控件的邊緣做文章即可,拿半圓形邊緣來說,我們只需要在一條邊上循環進行半圓的繪制即可,這裡重要的是要計算出這一條邊上有多少個半圓型,因為每個人可能設置的控件長度不一樣,我們可以指定一下圓形半徑以及半圓形與半圓形直接間距,有了這兩個參數就可以計算出這一條邊上有多少個半圓型了,如下計算方法:
/**
* 計算垂直方向需要畫圓或三角形的數量和初始偏移量
* @param gapSize 每個圓形或三角形之間的間距
*/
private void calculateVerticalCount(float gapSize){
mVerticalCount = (int) ((mHeight - gapSize) / (2 * mRadius + gapSize));
mVerticalInitSize = (int) ((mHeight - (2 * mRadius * mVerticalCount + (mVerticalCount + 1) * gapSize)) / 2);
}
/**
* 計算水平方向上圓或三角形的數量和初始偏移量
* @param gapSize 每個圓形或三角形之間的間距
*/
private void calculateHorizontalCount(float gapSize) {
mHorizontalCount = (int) ((mWidth - gapSize) / (2 * mRadius + gapSize));
mHorizontalInitSize = (int) ((mWidth - (2 * mRadius * mHorizontalCount + (mHorizontalCount + 1) * gapSize)) / 2);
}
上面這個計算方式畫個圖就出來了,假設有n個半圓,那就會有n+1個圓間距,因此控件寬度mWidth = n * 2 * radius + (n-1) * gapSize,由此可以求出半圓的個數,由於邊的寬度或高度在這個半徑radius和圓間距gapsize下不一定能夠完整平分,為了讓兩邊留出的距離相等,我們計算了一個初始偏移值。
在計算出了邊上的半圓數量之後,就可以開始准備繪制操作了。那繪制的時機是在哪裡?根據View的繪制過程:先繪制背景、在繪制自己(即onDraw)、下面繪制子布局、最後再繪制一些裝飾之類的,其實我們在onDraw或dispatchDraw中繪制都可以,只不過onDraw一般是對於View來說的,對於ViewGroup我們一般用它來擺放布局,當我們在ViewGroup的onDraw中繪制時,最好要給這個ViewGroup設置background或setWillNotDraw(false);進行設置,才能保證每次都會進入onDraw方法,這裡我選擇在dispatchDraw中進行繪制,先讓它的子View全部繪制完畢,然後再繪制邊緣,以避免我們的邊緣被裡面的內容給覆蓋掉。
在繪制的時候不斷循環繪制,不斷更改坐標即可。具體實現如下:
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if(vertical_style == 1){//如果垂直方向是半圓形
drawVerticalCircle(canvas);
}else if(vertical_style == 2){//垂直方向是三角形
drawVerticalTriangle(canvas);
}
if(horizontal_style == 1){//如果水平方向是半圓形
drawHorizontalCircle(canvas);
}else if(horizontal_style == 2){//如果水平方向是三角形
drawHorizontalTriangle(canvas);
}
}
/**
* 在水平方向上繪制三角形
* @param canvas
*/
private void drawHorizontalTriangle(Canvas canvas) {
//先計算出水平方向上的數量
calculateHorizontalCount(0);
Path path = new Path();
float x = 0;
//繪制上面部分
for(int i = 0; i < mHorizontalCount; i++){
path.reset();
x = mHorizontalInitSize + i * 2 * mRadius;
path.moveTo(x, 0);
x += mRadius;
path.lineTo(x, mRadius);
x += mRadius;
path.lineTo(x, 0);
path.close();
canvas.drawPath(path, mPaint);
}
//繪制下面部分
x = 0;
for(int i = 0; i < mHorizontalCount; i++){
path.reset();
x = mHorizontalInitSize + i * 2 * mRadius;
path.moveTo(x, mHeight);
x += mRadius;
path.lineTo(x, mHeight - mRadius);
x += mRadius;
path.lineTo(x, mHeight);
path.close();
canvas.drawPath(path, mPaint);
}
}
/**
* 在水平方向上繪制圓形
* @param canvas
*/
private void drawHorizontalCircle(Canvas canvas) {
//先計算出水平方向上的數量
calculateHorizontalCount(mGapSize);
float x = mHorizontalInitSize + mGapSize + mRadius;
//先繪制上面部分
for(int i = 0; i < mHorizontalCount; i++){
canvas.drawCircle(x, 0, mRadius, mPaint);
x += 2 * mRadius + mGapSize;
}
//再繪制下面部分
x = mHorizontalInitSize + mGapSize + mRadius;
for(int i = 0; i < mHorizontalCount; i++){
canvas.drawCircle(x, mHeight, mRadius, mPaint);
x += 2 * mRadius + mGapSize;
}
}
/**
* 在垂直方向繪制三角形
* @param canvas
*/
private void drawVerticalTriangle(Canvas canvas) {
//計算一下三角形的數量和初始距離
calculateVerticalCount(0);
Path path = new Path();
float y = 0;
//先畫左邊
for(int i = 0; i < mVerticalCount; i++){
path.reset();
y = mVerticalInitSize + i * 2 * mRadius;
path.moveTo(0, y);
y += mRadius;
path.lineTo(mRadius, y);
y += mRadius;
path.lineTo(0, y);
path.close();
canvas.drawPath(path, mPaint);
}
//再畫右邊
y = 0;
for(int i = 0; i < mVerticalCount; i++){
path.reset();
y = mVerticalInitSize + i * 2 * mRadius;
path.moveTo(mWidth, y);
y += mRadius;
path.lineTo(mWidth - mRadius, y);
y += mRadius;
path.lineTo(mWidth, y);
path.close();
canvas.drawPath(path, mPaint);
}
}
/**
* 在垂直方向繪制半圓形
* @param canvas
*/
private void drawVerticalCircle(Canvas canvas) {
//計算一下圓形的數量和初始偏移距離
calculateVerticalCount(mGapSize);
//這次使用畫弧來繪制出圓形
RectF rectF = new RectF();
//先畫左邊
for(int i = 0; i < mVerticalCount; i++){
rectF.left = -mRadius;
rectF.top = mVerticalInitSize + mGapSize * (i + 1) + i * 2 * mRadius;
rectF.right = mRadius;
rectF.bottom = rectF.top + 2 * mRadius;
canvas.drawArc(rectF, -90, 180, false, mPaint);
}
//再畫右邊
for(int i = 0; i < mVerticalCount; i++){
rectF.left = mWidth - mRadius;
rectF.top = mVerticalInitSize + mGapSize * (i + 1) + i * 2 * mRadius;
rectF.right = rectF.left + 2 * mRadius;
rectF.bottom = rectF.top + 2 * mRadius;
canvas.drawArc(rectF, 90, 180, false, mPaint);
}
}
因為這個自定義View是繼承自LinearLayout,在布局中直接把它當做LinearLayout來使用,通過我們自定義的屬性來指定邊緣類型。
那在xml布局中就可以自己自由指定了,如下:
android:layout_width="match_parent" android:layout_height="200dp" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:orientation="horizontal" android:background="#47BDBD" android:gravity="center_vertical" coupon:horizontal_style="circle" coupon:vertical_style="triangle"> android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:padding="30dp" android:orientation="vertical"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/white" android:textSize="18sp" android:text="順旺基優惠券"/> android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FC4A36" android:layout_marginTop="15dp" android:textSize="16sp" android:text="全場五折優惠"/> android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/white" android:layout_marginTop="15dp" android:textSize="15sp" android:text="券編號:2016070920160720"/> android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/white" android:layout_marginTop="15dp" android:textSize="15sp" android:text="有效期:2016-07-09至2016-07-20"/> android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="20dp" android:src="@mipmap/iv_coupon" android:layout_gravity="center"/>
點擊下載
使用起來是不是很簡單呢?
在翻看購物類APP時,看到了很多APP在添加購物車時有很多添加動畫,那麼下一篇就實現一下購物車添加動畫玩玩。
POJ 1365 Prime Land
Prime Land Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 2972
溫故知新-Service學習筆記
1.ServicesService 是一個可以在後台執行長時間運行操作而不使用用戶界面的應用組件。服務可由其他應用組件啟動,而且即使用戶切換到其他應用,服務仍將在後台繼續
Android多媒體-人臉識別
1. 相關背景 Google 於2006年8月收購Neven Vision 公司 (該公司擁有 10 多項應用於移動設備領域的圖像識別的專利),以此獲得了圖像識別的技術
Android基於service實現音樂的後台播放功能示例
本文實例講述了Android基於service實現音樂的後台播放功能。分享給大家供大家參考,具體如下:Service是一個生命周期長且沒有用戶界面的程序,當程序在各個ac