Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義View實現天氣折線圖效果

自定義View實現天氣折線圖效果

編輯:關於Android編程

本次博客主要介紹一個天氣效果的實現過程

最近公司的項目加入天氣模塊,需要實現下面的效果:

設計圖

 

然後根據自己的構想實現了下面的效果:

 

實現的效果圖

 

下面會詳細的介紹實現的過程。

1.構想思路

其實在拿到設計的一個效果,我們首先要做的就是去思考,怎麼實現,就算不好實現,也要實現一個折中的兩邊都可以妥協的方案。

由於當前是要展示10天以上的天氣的情況,那麼如果采用一個view繪制的形式肯定會影響到性能,那其實很快就想到了ListView,這不就是一個橫向的ListView的效果麼,考慮到ListView並沒有橫向的效果,轉而就想到了RecyclerView,RecyclerView的LinearLayoutManager可以直接設置為橫向的,這解決了使用什麼來實現的問題。

1.1最高溫度和最低溫度在View上面怎麼繪制

這個問題不是太難,可以這樣,拿到這15天的最低溫度和最高溫度,這個需要我們計算,從這15個最高以及最低溫度裡面找到最大以及最小的,然後使用這兩個溫度差去映射View的高度,最後可以得到需要的繪制的圓點View的Y軸的計算公式:
需要在View上面繪制的高度=(今天的溫度?15天裡面最低溫度)View的高度15天裡面最高溫度?15天裡面最低溫度

1.2折線在每個view的最左邊個最右邊的位置

這個也很簡單,用 昨天的溫度+今天的溫度2
然後再經過上面的計算就可以得到需要繪制的Y軸的位置。

2.具體實現

2.1WeatherLineView的實現

自定義WeatherLineView,需要下面的幾個屬性:

    
        
        
        
        
        
        
        
        
        
        
    

具體的實現:

public class WeatherLineView extends View {

    /**
     * 默認最小寬度50dp
     */
    private static final int defaultMinWidth = 100;

    /**
     * 默認最小高度80dp
     */
    private static final int defaultMinHeight = 80;

    /**
     * 字體最小默認16dp
     */
    private int mTemperTextSize = 16;

    /**
     * 文字顏色
     */
    private int mWeaTextColor = Color.BLACK;

    /**
     * 線的寬度
     */
    private int mWeaLineWidth = 1;

    /**
     * 圓點的寬度
     */
    private int mWeaDotRadius = 5;

    /**
     * 文字和點的間距
     */
    private int mTextDotDistance = 5;

    /**
     * 畫文字的畫筆
     */
    private TextPaint mTextPaint;

    /**
     * 文字的FontMetrics
     */
    private Paint.FontMetrics mTextFontMetrics;

    /**
     * 畫點的畫筆
     */
    private Paint mDotPaint;

    /**
     * 畫線的畫筆
     */
    private Paint mLinePaint;

    /**
     * 15天最低溫度的數據
     */
    private int mLowestTemperData;

    /**
     * 15天最高溫度的數據
     */
    private int mHighestTemperData;

    /**
     * 分別代表最左邊的,中間的,右邊的三個當天最低溫度值
     */
    private int mLowTemperData[];

    private int mHighTemperData[];

    public WeatherLineView(Context context) {
        this(context, null);
    }

    public WeatherLineView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WeatherLineView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
        initPaint();
    }

    /**
     * 設置當天的三個低溫度數據,中間的數據就是當天的最低溫度數據,
     * 第一個數據是當天和前天的數據加起來的平均數,
     * 第二個數據是當天和明天的數據加起來的平均數
     *
     * @param low  最低溫度
     * @param high 最高溫度
     */
    public void setLowHighData(int low[], int high[]) {
        mLowTemperData = low;
        mHighTemperData = high;
        invalidate();
    }

    /**
     * 設置15天裡面的最低和最高的溫度數據
     *
     * @param low  最低溫度
     * @param high 最高溫度
     */
    public void setLowHighestData(int low, int high) {
        mLowestTemperData = low;
        mHighestTemperData = high;
        invalidate();
    }

    /**
     * 設置畫筆信息
     */
    private void initPaint() {
        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextSize(mTemperTextSize);
        mTextPaint.setColor(mWeaTextColor);
        mTextFontMetrics = mTextPaint.getFontMetrics();

        mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mDotPaint.setStyle(Paint.Style.FILL);
        mDotPaint.setColor(mWeaTextColor);

        mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLinePaint.setStyle(Paint.Style.STROKE);
        mLinePaint.setStrokeWidth(mWeaLineWidth);
        mLinePaint.setColor(mWeaTextColor);
    }

    /**
     * 獲取自定義屬性並賦初始值
     */
    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WeatherLineView,
                defStyleAttr, 0);
        mTemperTextSize = (int) a.getDimension(R.styleable.WeatherLineView_temperTextSize,
                dp2px(context, mTemperTextSize));
        mWeaTextColor = a.getColor(R.styleable.WeatherLineView_weatextColor, Color.parseColor("#b07b5c"));
        mWeaLineWidth = (int) a.getDimension(R.styleable.WeatherLineView_weaLineWidth,
                dp2px(context, mWeaLineWidth));
        mWeaDotRadius = (int) a.getDimension(R.styleable.WeatherLineView_weadotRadius,
                dp2px(context, mWeaDotRadius));
        mTextDotDistance = (int) a.getDimension(R.styleable.WeatherLineView_textDotDistance,
                dp2px(context, mTextDotDistance));
        a.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int width = getSize(widthMode, widthSize, 0);
        int height = getSize(heightMode, heightSize, 1);
        setMeasuredDimension(width, height);
    }

    /**
     * @param mode Mode
     * @param size Size
     * @param type 0表示寬度,1表示高度
     * @return 寬度或者高度
     */
    private int getSize(int mode, int size, int type) {
        // 默認
        int result;
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            if (type == 0) {
                // 最小不能低於最小的寬度
                result = dp2px(getContext(), defaultMinWidth) + getPaddingLeft() + getPaddingRight();
            } else {
                // 最小不能小於最小的寬度加上一些數據
                int textHeight = (int) (mTextFontMetrics.bottom - mTextFontMetrics.top);
                // 加上2個文字的高度
                result = dp2px(getContext(), defaultMinHeight) + 2 * textHeight +
                        // 需要加上兩個文字和圓點的間距
                        getPaddingTop() + getPaddingBottom() + 2 * mTextDotDistance;
            }

            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mLowTemperData == null || mHighTemperData == null
                || mLowestTemperData == 0 || mHighestTemperData == 0) {
            return;
        }

        canvas.drawColor(Color.YELLOW);

        // 文本的高度
        int textHeight = (int) (mTextFontMetrics.bottom - mTextFontMetrics.top);

        // 一個基本的高度,由於最下面的時候,有文字和圓點和文字的寬度需要留空間
        int baseHeight = getHeight() - textHeight - mTextDotDistance;

        // 最低溫度相關
        // 最低溫度中間
        int calowMiddle = baseHeight - cacHeight(mLowTemperData[1]);
        canvas.drawCircle(getWidth() / 2, calowMiddle, mWeaDotRadius, mDotPaint);

        // 畫溫度文字
        String text = String.valueOf(mLowTemperData[1]) + "°";
        int baseX = (int) (canvas.getWidth() / 2 - mTextPaint.measureText(text) / 2);
        // mTextFontMetrics.top為負的
        // 需要加上文字高度和文字與圓點之間的空隙
        int baseY = (int) (calowMiddle - mTextFontMetrics.top) + mTextDotDistance;
        canvas.drawText(text, baseX, baseY, mTextPaint);

        if (mLowTemperData[0] != 0) {
            // 最低溫度左邊
            int calowLeft = baseHeight - cacHeight(mLowTemperData[0]);
            canvas.drawLine(0, calowLeft, getWidth() / 2, calowMiddle, mLinePaint);
        }

        if (mLowTemperData[2] != 0) {
            // 最低溫度右邊
            int calowRight = baseHeight - cacHeight(mLowTemperData[2]);
            canvas.drawLine(getWidth() / 2, calowMiddle, getWidth(), calowRight, mLinePaint);
        }

        // 最高溫度相關
        // 最高溫度中間
        int calHighMiddle = baseHeight - cacHeight(mHighTemperData[1]);
        canvas.drawCircle(getWidth() / 2, calHighMiddle, mWeaDotRadius, mDotPaint);

        // 畫溫度文字
        String text2 = String.valueOf(mHighTemperData[1]) + "°";
        int baseX2 = (int) (canvas.getWidth() / 2 - mTextPaint.measureText(text2) / 2);
        int baseY2 = (int) (calHighMiddle - mTextFontMetrics.bottom) - mTextDotDistance;
        canvas.drawText(text2, baseX2, baseY2, mTextPaint);

        if (mHighTemperData[0] != 0) {
            // 最高溫度左邊
            int calHighLeft = baseHeight - cacHeight(mHighTemperData[0]);
            canvas.drawLine(0, calHighLeft, getWidth() / 2, calHighMiddle, mLinePaint);
        }

        if (mHighTemperData[2] != 0) {
            // 最高溫度右邊
            int calHighRight = baseHeight - cacHeight(mHighTemperData[2]);
            canvas.drawLine(getWidth() / 2, calHighMiddle, getWidth(), calHighRight, mLinePaint);
        }
    }

    private int cacHeight(int tem) {
        // 最低,最高溫度之差
        int temDistance = mHighestTemperData - mLowestTemperData;
        int textHeight = (int) (mTextFontMetrics.bottom - mTextFontMetrics.top);
        // view的最高和最低之差,需要減去文字高度和文字與圓點之間的空隙
        int viewDistance = getHeight() - 2 * textHeight - 2 * mTextDotDistance;
        // 今天的溫度和最低溫度之間的差別
        int currTemDistance = tem - mLowestTemperData;
        return currTemDistance * viewDistance / temDistance;
    }

    public static int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, context.getResources().getDisplayMetrics());
    }

}

上面就是折線的天氣View,代碼的注釋比較詳細了。

2.2RecyclerView的Item的布局




    

    

        

        

        

        

        

        

        

    

    

預覽的效果是這樣的:
預覽
當然這裡那些屬性我沒有加上去,因為代碼裡面有設置默認值,如果覺得不滿足要求的話,可以自己設置。

2.3RecyclerView的adapter的實現

public class WeaDataAdapter extends RecyclerView.Adapter {

    private Context mContext;
    private LayoutInflater mInflater;
    private List mDatas;
    private int mLowestTem;
    private int mHighestTem;

    public WeaDataAdapter(Context context, List datats, int lowtem, int hightem) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mDatas = datats;
        mLowestTem = lowtem;
        mHighestTem = hightem;
    }

    @Override
    public WeatherDataViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.item_weather_item, parent, false);
        WeatherDataViewHolder viewHolder = new WeatherDataViewHolder(view);
        viewHolder.dayText = (TextView) view.findViewById(R.id.id_day_text_tv);
        viewHolder.dayIcon = (ImageView) view.findViewById(R.id.id_day_icon_iv);
        viewHolder.weatherLineView = (WeatherLineView) view.findViewById(R.id.wea_line);
        viewHolder.nighticon = (ImageView) view.findViewById(R.id.id_night_icon_iv);
        viewHolder.nightText = (TextView) view.findViewById(R.id.id_night_text_tv);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(WeatherDataViewHolder holder, int position) {
        // 最低溫度設置為15,最高溫度設置為30
        Resources resources = mContext.getResources();
        WeatherDailyModel weatherModel = mDatas.get(position);
        holder.dayText.setText(weatherModel.getText_day());
        int iconday = resources.getIdentifier("wth_code_" + weatherModel.getCode_day(), "drawable", mContext.getPackageName());
        if (iconday == 0) {
            holder.dayIcon.setImageResource(R.drawable.wth_code_99);
        } else {
            holder.dayIcon.setImageResource(iconday);
        }
        holder.weatherLineView.setLowHighestData(mLowestTem, mHighestTem);
        int iconight = resources.getIdentifier("wth_code_" + weatherModel.getCode_day(), "drawable", mContext.getPackageName());
        if (iconight == 0) {
            holder.nighticon.setImageResource(R.drawable.wth_code_99);
        } else {
            holder.nighticon.setImageResource(iconight);
        }
        holder.nightText.setText(weatherModel.getText_night());
        int low[] = new int[3];
        int high[] = new int[3];
        low[1] = weatherModel.getLow();
        high[1] = weatherModel.getHigh();
        if (position <= 0) {
            low[0] = 0;
            high[0] = 0;
        } else {
            WeatherDailyModel weatherModelLeft = mDatas.get(position - 1);
            low[0] = (weatherModelLeft.getLow() + weatherModel.getLow()) / 2;
            high[0] = (weatherModelLeft.getHigh() + weatherModel.getHigh()) / 2;
        }
        if (position >= mDatas.size() - 1) {
            low[2] = 0;
            high[2] = 0;
        } else {
            WeatherDailyModel weatherModelRight = mDatas.get(position + 1);
            low[2] = (weatherModel.getLow() + weatherModelRight.getLow()) / 2;
            high[2] = (weatherModel.getHigh() + weatherModelRight.getHigh()) / 2;
        }
        holder.weatherLineView.setLowHighData(low, high);
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    public static class WeatherDataViewHolder extends RecyclerView.ViewHolder {

        TextView dayText;
        ImageView dayIcon;
        WeatherLineView weatherLineView;
        ImageView nighticon;
        TextView nightText;

        public WeatherDataViewHolder(View itemView) {
            super(itemView);
        }
    }
}

Model裡面的字段:

        public static class WeatherDailyModel {
            /**
             * date : 2016-05-30
             * text_day : 多雲
             * code_day : 4
             * text_night : 陰
             * code_night : 9
             * high : 34
             * low : 22
             */
            private String date;
            private String text_day;
            private int code_day;
            private String text_night;
            private int code_night;
            private int high;
            private int low;
        }

2.4Activity裡面獲取數組設置到RecyclerView裡面去

    private void initView() {
        //得到控件
        mRecyclerView = (RecyclerView) findViewById(R.id.id_recyclerview_horizontal);
        //設置布局管理器
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        mRecyclerView.setLayoutManager(layoutManager);

    }
    private void fillDatatoRecyclerView(List daily) {
        mWeatherModels = daily;
        Collections.sort(daily, new Comparator() {
            @Override
            public int compare(WeatherDailyModel lhs,
                               WeatherDailyModel rhs) {
                // 排序找到溫度最低的,按照最低溫度升序排列
                return lhs.getLow() - rhs.getLow();
            }
        });

        int low = daily.get(0).getLow();

        Collections.sort(daily, new Comparator() {
            @Override
            public int compare(WeatherDailyModel lhs,
                               WeatherDailyModel rhs) {
                // 排序找到溫度最高的,按照最高溫度降序排列
                return rhs.getHigh() - lhs.getHigh();
            }
        });
        int high = daily.get(0).getHigh();

        mWeaDataAdapter = new WeaDataAdapter(this, mWeatherModels, low, high);
        mRecyclerView.setAdapter(mWeaDataAdapter);
    }

這樣其實就搞定了,WeatherLineView可能比較麻煩一點吧,但是只要是想清楚了,就很好了,從上面也看到WeatherLineView沒什麼含金量的,就是很普通的繪制。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved