分析自定義view的實現過程-實現雪花飛舞效果

CelindaBlac 8年前發布 | 11K 次閱讀 安卓開發 Android開發 移動開發

原文出處: http://www.cnblogs.com/jww-love-study/p/5114028.html

聲明:本文源碼出自實現雪花飛舞效果(有改動)主要通過這篇文來分析自定義view的實現過程。

沒事時,比較喜歡上網看看一些新的東西,泡在網上的日子就是一個很不錯的網站。

下面開始了,哈哈。^_^

大家都知道,自定義view分成三個類型,1、是完全自定義,自己繪制,例如本文講的例子。2、是Groupview,就是把一些安卓原生提供的控件組合起來,做成一個有多種功能的組合控件,如前面寫過的 android-oldman之TitleBar .就是這種。3、就是繼承自安卓原來的控件,然后增加修改成自己需要的功能。如extends TextVeiw后,再里面做一些更改,text的位置,長度什么的。相比2,與3,第1種自定義view就是要繼承自view,然后根據需要,測量,繪制。

最終要的效果是:看到沒有,上面一些彩色的點,就是原來的雪花,當然,你可以通過修改參數,改變雪花數量。

下面就開始貼代碼了,哈哈哈。

首先,先是定義view

package com.example.jwwsnow;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class SnowView extends View {

    Random random;
    private static final int NUM_SNOWFLAKES = 15;//雪花數量
    private static final int DELAY = 1;//畫面延時刷新時間

    private SnowFlake[] snowflakes;//雪花對像數組。

    public SnowView(Context context) {
        super(context);
    }

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

    public SnowView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    protected void resize(int width, int height) {
        random = new Random();
        snowflakes = new SnowFlake[NUM_SNOWFLAKES];
        for (int i = 0; i < NUM_SNOWFLAKES; i++) {
            //for循環生產雪花。
            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
            paint.setStyle(Paint.Style.FILL);
            paint.setColor(Color.rgb(random.getColor(), random.getColor(), random.getColor()));
            //返回的對象存入對象數組中去。
            snowflakes[i] = SnowFlake.create(width, height, paint);
            Log.i("SnowDemo", "時間::::::::::"+System.currentTimeMillis());
        }
    }

    /**
     * View中方法的啟動順序onSizeChanged()>onDraw();
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (w != oldw || h != oldh) {
            resize(w, h);
        }

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (SnowFlake snowFlake : snowflakes) {
            //得到雪花對象,繪制。
            snowFlake.draw(canvas);
        }

        getHandler().postDelayed(runnable, DELAY);//得到子線程,設置5ms延時,每5ms在主線程繪制一次界面。。
    }

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            invalidate();//此方法會把原來的視圖清除掉,并重新調用veiw.onDraw方法。
        }
    };
}

可以看到,重寫了view中的幾個方法:

onSizeChanged();
這個方法,是先于onDrow方法執行的。判斷view的size是不是改變,如果改變,則由系統調用。然后才是執行onDrow()方法。
在本例此方法中,系統通過對view的尺寸的判斷,來調用reSize()方法,并把width與heigh傳遞過去。
在reSize()方法中。定義了雪花對象數組SnowFlakes[],通過for循環,創建指定數量的雪花對象,并在for循環中創建Paint對象,設置畫筆。(ps:Paint與Canvas,paint就像我們平時做畫用的畫筆,我們可以選擇畫筆的顏色,粗細,抗鋸齒,空心,實心,是不是帶陰影等。Canvas,畫布,我們可以用畫布來承載我們要畫的具體事物,如矩形,圓形,線等。要把ptint與canvas區分開,因為功用不同。canvas決定要畫的具體是什么,print決定用什么樣的性質去畫嘿嘿,被我說暈沒。)。然后調用下面要貼的雪花Calss的create方法。這個方法主要是設置一些尺寸,位置等的。然后把這些尺寸位置等參數狀態以對象的形式保存在SnowFalkes[]數組中,供下面的onDraw方法中去用這些參數做畫。

onDraw()方法中,for循環,依次對SnowFalkes[]對象數組中的每個雪花進行繪畫。然后用Handler在主線程中定時重繪一次。
snowFlake.draw(canvas);方法則是具體的進行繪制了。下面先貼代碼,再接著講。
package com.example.jwwsnow;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;

public class SnowFlake {

    private static final float ANGE_RANGE = 0.1f;    //
    private static final float HALF_ANGLE_RANGE = ANGE_RANGE / 2f;
    private static final float HALF_PI = (float) Math.PI / 2f;
    private static final float ANGLE_SEED = 25f;
    private static final float ANGLE_DIVISOR = 10000f;
    private static final float INCREMENT_LOWER = 2f;
    private static final float INCREMENT_UPPER = 4f;
    private static final float FLAKE_SIZE_LOWER = 7f;//最小雪花大小
    private static final float FLAKE_SIZE_UPPER = 20f;//最大雪花大小

    private final Random random;
    private final Point position;
    private float angle;
    private final float increment;
    private final float flakeSize;
    private final Paint paint;

    public static SnowFlake create(int width, int height, Paint paint) {
        Random random = new Random();
        int x = random.getRandom(width);//得到[0~width)的整數width與height都是外層view的尺寸。
        int y = random.getRandom(height);
        Point position = new Point(x, y);//設置雪花剛一出來的隨機位置。
        //設置Random.getRandom(ANGLE_SEED)/ANGLE_SEED得到[0~1)再*ANGE_RANGE得到[0~0.1f)的數據,再減去0.05得到[-0.05~0.05)的數據。
        float angle = random.getRandom(ANGLE_SEED) / ANGLE_SEED * ANGE_RANGE + HALF_PI - HALF_ANGLE_RANGE;
        //得到[2f~4f)的隨機數據
        float increment = random.getRandom(INCREMENT_LOWER, INCREMENT_UPPER);
        //得到[7f~20f)的隨機數據
        float flakeSize = random.getRandom(FLAKE_SIZE_LOWER, FLAKE_SIZE_UPPER);
        //返回雪花對象。
        return new SnowFlake(random, position, angle, increment, flakeSize, paint);
    }

    SnowFlake(Random random, Point position, float angle, float increment, float flakeSize, Paint paint) {
        this.random = random;
        this.position = position;
        this.angle = angle;//[-0.05~0.05)
        this.increment = increment;//[2f~4f)
        this.flakeSize = flakeSize;//[7f~20f)
        this.paint = paint;
    }

    private void move(int width, int height) {
        //x方向的偏移量小,y方向的大,是為了讓雪花快點落下。
        double x = position.x + (increment * Math.cos(angle));//Math.cos(angle)約為1
        double y = position.y + (increment * Math.sin(angle));//Math.sin(0.05~-0.05) = +-[0~8.7)
      /*
       *設置雪花的位置為正,不跑出屏幕。[0~1)*2*ANGLE_SEED-ANGLE_SEED等于+-(0~ANGLE_SEED],為+-(0~25f]。然后再/10000f====+-(0~0.0025],
       *然后再加[-0.05~0.05)泥媽,快算暈了。大神們果然不好理解。(-0.0525~0.0525) 
       */

        angle += random.getRandom(-ANGLE_SEED, ANGLE_SEED) / ANGLE_DIVISOR;

        position.set((int) x, (int) y);//設置新的位置

        if (!isInside(width, height)) {
            //如果雪花不在view視圖內,則重設置他們的位置。
            reset(width);
        }
    }

    private boolean isInside(int width, int height) {
        //TODO 設置雪花位置
        int x = position.x;
        int y = position.y;
        //判斷x坐標x>雪花尺寸加1距原點的距離(負方向)。x<background寬,同樣的方式理解y。由于上面的設置,其實x方向,雪花是不可能跑出屏幕的,只有y方向上會。
        return x >= -flakeSize - 1 && x + flakeSize <= width && y >= -flakeSize - 1 && y - flakeSize < height;

    }

    private void reset(int width) {
        //當雪花落到屏幕最下方以下時,重新設置雪花從
        position.x = random.getRandom(width);//x軸設設置一個view內的隨機位置就行
        position.y = (int) (-flakeSize - 1);//view的(0,0)點在view的左上角,所以,當y為負時,則在view的上方。看起來像是一個新的雪花從上落下。
        angle = random.getRandom(ANGLE_SEED) / ANGLE_SEED * ANGE_RANGE + HALF_PI - HALF_ANGLE_RANGE;
    }

    public void draw(Canvas canvas) {
        //繪制方法 
        int width = canvas.getWidth();
        int height = canvas.getHeight();//雪花所整個view高度與寬度
        move(width, height);//雪花移動,傳的參數為view的的北景大小。
      //畫一個個雪花。其實就是畫一個個球。其坐標,尺寸,畫筆都是原來在create方法時就定義 了的,之后保存在flakesnow數組里了。
        canvas.drawCircle(position.x, position.y, flakeSize, paint);
    }


}

先跳到前面的onSizeChange()方法來說。可以看到,當調用SnowFlake.create()方法后,最后返回的是:(里面的Random對象也是重新進行封裝的,馬上貼上)

return new SnowFlake(random, position, angle, increment, flakeSize, paint);

這個是用來繪制SnowFlake時需要的參數。然后在onSizeChange()方法調用的reSize()方法中保存進入了雪花數組SnowFlakes[i]。

再跳到上面大段代碼前面的接著說,在onDraw()方法中,for循環會依次繪制每個雪花。snowFalke.draw().就是snowflakes[i] = SnowFlake snowFlake();

snowFlake再調用自己的draw()方法。由于前面每個SnowFlake對象都保存了每個雪花的參數,所以在draw()中,用的就直接使用了。

雪花是要下落的,則在draw()方法中,調用move()方法,來設置每個雪花的位置。當雪花位置跑出屏幕后再調用reset()方法,重新設置雪花從屏幕最上方重新落下。

下面是代碼中用來設置隨機數據的Random對象封裝。

package com.example.jwwsnow;

import java.util.ArrayList;
import java.util.List;

public class Random {

     private static final java.util.Random RANDOM = new java.util.Random();

        public float getRandom(float lower, float upper) {
            float min = Math.min(lower, upper);//返回兩者較小的一個。
            float max = Math.max(lower, upper);
            return getRandom(max - min) + min;//返回的是比最大的小,比最小的大的數。
        }

        public float getRandom(float upper) {
            return RANDOM.nextFloat() * upper;//Random.nextFloat()生成[0~1)的數.
        }

        public int getRandom(int upper) {
            return RANDOM.nextInt(upper);//隨機生成比[0~upper)的數值。
        }
        public int getColor(){
            return RANDOM.nextInt(255);//隨機生成[0~255)整數。

        }
}

然后就是使用了:xml引用,要把路徑寫完整。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.stylingandroid.snowfall.MainActivity">

  <ImageView
    android:id="@+id/image"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerInParent="true"
    android:contentDescription="@null"
    android:scaleType="fitCenter"
    android:src="@drawable/tree" />

  <com.example.jwwsnow.SnowView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_alignBottom="@id/image"
    android:layout_alignEnd="@id/image"
    android:layout_alignLeft="@id/image"
    android:layout_alignRight="@id/image"
    android:layout_alignStart="@id/image"
    android:layout_alignTop="@id/image" />
</RelativeLayout

本文主要用來復習自定義view的流程。建議先看原文。^_^

下面把我注釋改變后的一些代碼發上來,僅做研究用(eclipse版的。。。)。

ca,博客園不能上傳代碼。 360云盤 訪問密碼 ce1b。

來自: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0121/3901.html

 本文由用戶 CelindaBlac 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!