Fold-line chart of trend forecast in the next few days for imitating millet weather forecast

Keywords: Android less xml encoding

Don't talk too much nonsense, paste the effect map and code directly...

The results are as follows:

First of all, let me talk about my idea of implementation. An external Horizontal ScrollView adds a LinearLayout sub-control container to scrollView, traverses the loop to add a daily weather layout to LinearLayout, and sets the data for each day for view. Data from https://www.nowapi.com/api/weather.future

Paste code below:
First is the layout layout of the day-to-day weather.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal">
    <TextView
        android:id="@+id/tv_horizontal_item_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Monday\n2016-01-11"
        android:gravity="center_horizontal"/>
    <ImageView
        android:id="@+id/iv_horizontal_item_day_pic"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:src="@mipmap/ic_launcher"
        />
    <TextView
        android:id="@+id/tv_horizontal_item_day_weather"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Fine"
        android:layout_marginBottom="6dp"/>
    <com.example.cyy.weather.widget.FutureWeatherTrendView
        android:id="@+id/view_horizontal_item_trend"
        android:layout_width="120dp"
        android:layout_height="120dp" />
    <ImageView
        android:id="@+id/iv_horizontal_item_night_pic"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:src="@mipmap/ic_launcher"
        android:layout_marginTop="6dp"/>
    <TextView
        android:id="@+id/tv_horizontal_item_night_weather"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Fine"/>
    <TextView
        android:id="@+id/tv_horizontal_item_wind"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Southwest wind\n3 level"
        android:layout_marginTop="9dp"
        android:gravity="center_horizontal"/>

</LinearLayout>

The preview is like this.

The middle part is a custom view. The bottom part is to see how the custom view is implemented, so that the custom view is suitable for any data. The general idea is:
1. Calculate the height of each degree, that is, the highest temperature and the lowest temperature in these days, subtract the two temperatures to get the maximum temperature difference, and divide the height of the view (excluding the height of the upper and lower text) by the temperature difference to get the value.
2. In order to connect with the temperature of the day before and the day after, it is necessary to know the median value of the temperature of the day before and the day after. According to the principle of similar triangle, the median value of the temperature of the day before and the day after and the median value of the temperature of the day and the day after are calculated respectively.
As shown in the picture:

If the first day is as follows (without knowing the median value from the previous day)

If it's the last day, it's as follows (you don't need to know the median with the next day)

3. Draw dots, write text, connect lines, calculate temperature points and Y coordinates on both sides according to the difference between minimum temperature, draw dots according to coordinates, write text, and finally connect lines.

package com.example.cyy.weather.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import com.example.cyy.weather.utils.Tools;

/**
 * Created by cyy on 2017/1/11.
 *
 * //A Custom view of Future Weather Trends
 */
public class FutureWeatherTrendView extends View {
    //Specify a default minimum width and default minimum height for the current view
    private int mDefLeastWidth = 80, mDefLeastHeight = 150;
    //Context object
    private Context mContext;
    //Brushes representing dots, texts and lines, respectively
    private Paint mCirclePaint, mTextPaint, mLinePaint;
    //font size
    private int mPaintTextSize = 16;
    //The radius of the point drawn
    private int mCircleRadius = 3;
    //Objects that contain font metrics
    private Paint.FontMetrics mFontMetrics;
    //The left, middle, right and middle temperatures of low temperature are the same day's temperatures, while the left is the middle value of the day and the day's temperatures.
    // The temperature on the right is the median of the day and the next.
    private float mLowTemArray[], mHighTemArray[];
    //Minimum and maximum temperatures over the next few days
    private static float mLowestTem, mHighestTem;
    //Y-coordinates of the lowest temperature points in the next few days
    private float mLowestTemY;
    //Spacing between text and point
    private int mTextCircleDis = 3;
    //Mark the first day and the last day, if type=0 represents the first day, type=1 represents the last day, and type=2 represents the day between the first day and the last day.
    private int mType;

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

    public FutureWeatherTrendView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        initPaint();
    }

    /**
     * Initialize various brushes
     */
    private void initPaint(){
        mCirclePaint = new Paint();
        mCirclePaint.setColor(Color.YELLOW);

        mTextPaint = new Paint();
        mTextPaint.setTextSize(Tools.sp2px(mContext, mPaintTextSize));
        mTextPaint.setColor(Color.BLACK);
        mFontMetrics = mTextPaint.getFontMetrics();

        mLinePaint = new Paint();
        mLinePaint.setStrokeWidth(3);
        mLinePaint.setColor(Color.BLUE);
        //Anti-Aliasing
        mLinePaint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //Width and Height of view
        int width, height;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        width = measureWidthHeight(widthMode, widthSize, 0);
        height = measureWidthHeight(heightMode, heightSize, 0);
        setMeasuredDimension(width, height);
    }

    /**
     * Measure the width and height of view
     * @param mode
     * @param size
     * @param tag 0 Width and height
     * @return
     */
    private int measureWidthHeight(int mode, int size, int tag) {
        int resultSize = 0;
        if(mode == MeasureSpec.EXACTLY){
            resultSize = size;
        }else{
            if(tag == 0){
                //The width of view should not be less than the minimum width
                resultSize = Tools.dip2px(mContext, mDefLeastWidth) + getPaddingLeft() + getPaddingRight();
            }else{
                //view height should not be less than minimum height
                resultSize = Tools.dip2px(mContext, mDefLeastHeight) + getPaddingTop() + getPaddingBottom();
            }
            if(mode == MeasureSpec.AT_MOST){
                resultSize = Math.min(size, resultSize);
            }
        }
        return resultSize;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //Low temperature Y coordinate
        float lowTemTextY = getTemY(mLowTemArray[1]) + getExtraHeight();
        drawLowOrHigh(canvas, mLowTemArray, lowTemTextY, mType);
        drawLowOrHigh(canvas, mHighTemArray, getTextHeight(), mType);

    }

    /**
     * Draw low and high temperature points, temperature text values and trends
     * @param canvas canvas
     * @param temArray Low temperature or high temperature left middle right temperature set
     * @param textY Y-coordinates of text at low or high temperatures
     */
    private void drawLowOrHigh(Canvas canvas, float temArray[], float textY, int type) {
        //The first step is to draw the low temperature point, which is the temperature point in the middle (the coordinates of the center of the circle need to be found).
        //X coordinate center
        int temX = getWidth() / 2;
        //Y coordinate center
        float temY = getTemY(temArray[1]);
        //Draw points
        canvas.drawCircle(temX, temY, Tools.dip2px(mContext, mCircleRadius), mCirclePaint);
        //The second step is to write the temperature value of the lower left corner of the text (starting from the lower left corner of the text, you need to find the coordinates of the lower left corner).
        //X coordinates
        float temTextX = getWidth() / 2 - mTextPaint.measureText(temArray[1] + "°") / 2;
        //Y coordinates
        float temTextY = textY;
        //Writing text
        canvas.drawText(temArray[1] + "°", temTextX, temTextY, mTextPaint);
        //The third step is to get the coordinates of the left and right points of the low temperature.
        int temLeftX = 0;
        float temLeftY = getTemY(temArray[0]);
        int temRightX = getWidth();
        float temRightY = getTemY(temArray[2]);
        //Step 4. Temperature points in the middle and connections on the left and right
        if(type == 0){
            canvas.drawLine(temX + Tools.dip2px(mContext, mCircleRadius), temY, temRightX, temRightY, mLinePaint);
        } else if(type == 1){
            canvas.drawLine(temLeftX, temLeftY, temX - Tools.dip2px(mContext, mCircleRadius), temY, mLinePaint);
        } else{
            canvas.drawLine(temLeftX, temLeftY, temX - Tools.dip2px(mContext, mCircleRadius), temY, mLinePaint);
            canvas.drawLine(temX + Tools.dip2px(mContext, mCircleRadius), temY, temRightX, temRightY, mLinePaint);
        }
    }

    /**
     * Height of text obtained
     * @return
     */
    private float getTextHeight(){
        //Height of text
        float textHeight = mFontMetrics.bottom - mFontMetrics.top;
        return textHeight;
    }

    /**
     * Text Height plus Spacing from Text to Temperature Point
     */
    private float getExtraHeight(){
        return getTextHeight() + Tools.dip2px(mContext, mTextCircleDis);
    }

    /**
     * Obtain the Y coordinate value of the point center corresponding to the lowest temperature in the next few days
     */
    private float getLowestTemY(){
        //The Y coordinate value of the lowest point = the height of the entire view minus the height of the underlying text and the distance between the text and the temperature point
        mLowestTemY = getHeight() - getExtraHeight();
        return mLowestTemY;
    }

    /**
     * Height occupied by average one temperature
     * @return
     */
    private float getPerTemHeight(){
        //Height difference between maximum and minimum temperatures
        float lowestHighestHeightDis = getHeight() - 2 * getExtraHeight();
        //The difference between the highest and lowest temperatures
        float lowestHighestTemDis = mHighestTem - mLowestTem;
        return lowestHighestHeightDis / lowestHighestTemDis;
    }

    /**
     * Obtaining the ordinates of tem temperature points
     * @param tem
     */
    private float getTemY(float tem){
        float temY = getLowestTemY() - (tem - mLowestTem) * getPerTemHeight();
        return temY;
    }

    /**
     * Set low temperature and low temperature around the temperature value
     * @param lowTemArray
     */
    public FutureWeatherTrendView setLowTemArray(float lowTemArray[]){
        this.mLowTemArray = lowTemArray;
        return this;
    }

    /**
     * Set the temperature values at high and high temperatures
     * @param highTemArray
     */
    public FutureWeatherTrendView setHighTemArray(float highTemArray[]){
        this.mHighTemArray = highTemArray;
        return this;
    }

    public FutureWeatherTrendView setType(int type){
        this.mType = type;
        return this;
    }

    /**
     * Set the lowest temperature in the next few days
     * @param lowestTem
     */
    public static void setLowestTem(int lowestTem){
        mLowestTem = lowestTem;
    }

    /**
     * Set the highest temperature in the next few days
     * @param highestTem
     */
    public static void setHighestTem(int highestTem){
        mHighestTem = highestTem;
    }

}

Then set the data for view and add the view code to the container, as follows:

mTvPeriod.setText(weatherList.get(0).days + "---" + weatherList.get(weatherList.size() - 1).days);
        View itemView = null;
        FutureWeatherObj lastWeatherObj = null;
        FutureWeatherObj nextWeatherObj = null;
        int lowestTem = 0, highestTem = 0;
        LinearLayout ll = new LinearLayout(this);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        ll.setLayoutParams(params);
        ll.setOrientation(LinearLayout.HORIZONTAL);
        ll.removeAllViews();
        ArrayList<Integer> lowTemList = new ArrayList<Integer>();//Collection of cryogenic temperature values
        ArrayList<Integer> highTemList = new ArrayList<Integer>();//Collection of stored high temperature values
        for (int i = 0; i < weatherList.size(); i++){
            FutureWeatherObj weatherObj = weatherList.get(i);
            lowTemList.add(Integer.parseInt(weatherObj.temp_low));
            highTemList.add(Integer.parseInt(weatherObj.temp_high));
            Collections.sort(lowTemList);//Arrange all low temperature values in ascending order
            Collections.sort(highTemList);//Arrange all high temperature values in ascending order
        }
        FutureWeatherTrendView.setLowestTem(lowTemList.get(0));
        FutureWeatherTrendView.setHighestTem(highTemList.get(weatherList.size() - 1));
        for(int i = 0; i < weatherList.size(); i++){
            float lowTemArray[] = new float[3];
            float highTemArray[] = new float[3];
            float lastNextTem[] = new float[4];
            FutureWeatherObj weatherObj = weatherList.get(i);
            if(i == 0){ //Not the day before but the day after
                lastWeatherObj = null;
                nextWeatherObj = weatherList.get(i + 1);
            } else if(i > 0 && i < weatherList.size() - 1){ //The day before and the day after
                lastWeatherObj = weatherList.get(i - 1);
                nextWeatherObj = weatherList.get(i + 1);
            } else if(i == weatherList.size() - 1){ //Only the day before and the day after
                lastWeatherObj = weatherList.get(i - 1);
                nextWeatherObj = null;
            }
            if(lastWeatherObj != null) {
                lastNextTem[0] = Integer.parseInt(lastWeatherObj.temp_low);
                lastNextTem[1] = Integer.parseInt(lastWeatherObj.temp_high);
            }
            if(weatherObj != null){
                lowTemArray[1] = Integer.parseInt(weatherObj.temp_low);
                highTemArray[1] = Integer.parseInt(weatherObj.temp_high);
            }
            if(nextWeatherObj != null) {
                lastNextTem[2] = Integer.parseInt(nextWeatherObj.temp_low);
                lastNextTem[3] = Integer.parseInt(nextWeatherObj.temp_high);
            }
            if(i == 0){
                lowTemArray[2] = (lowTemArray[1] + lastNextTem[2]) / 2;
                highTemArray[2] = (highTemArray[1] + lastNextTem[3]) / 2;
            }else if(i > 0 && i < weatherList.size() - 1){
                lowTemArray[0] = (lastNextTem[0] + lowTemArray[1]) / 2;
                highTemArray[0] = (lastNextTem[1] + highTemArray[1]) / 2;
                lowTemArray[2] = (lowTemArray[1] + lastNextTem[2]) / 2;
                highTemArray[2] = (highTemArray[1] + lastNextTem[3]) / 2;
            }else if(i == weatherList.size() - 1){
                lowTemArray[0] = (lastNextTem[0] + lowTemArray[1]) / 2;
                highTemArray[0] = (lastNextTem[1] + highTemArray[1]) / 2;
            }
            itemView = LayoutInflater.from(this).inflate(R.layout.activity_future_weather_horizontal_item, null);
            TextView mTvDate = (TextView) itemView.findViewById(R.id.tv_horizontal_item_date);
            ImageView mIvDayPic = (ImageView) itemView.findViewById(R.id.iv_horizontal_item_day_pic);
            TextView mTvDayWeather = (TextView) itemView.findViewById(R.id.tv_horizontal_item_day_weather);
            FutureWeatherTrendView mViewTrend = (FutureWeatherTrendView) itemView.findViewById(R.id.view_horizontal_item_trend);
            ImageView mIvNightPic = (ImageView) itemView.findViewById(R.id.iv_horizontal_item_night_pic);
            TextView mTvNightWeather = (TextView) itemView.findViewById(R.id.tv_horizontal_item_night_weather);
            TextView mTvWind = (TextView) itemView.findViewById(R.id.tv_horizontal_item_wind);
            mTvDate.setText(weatherObj.week + "\n" + weatherObj.days);
            Picasso.with(this).load(weatherObj.weather_icon).into(mIvDayPic);
            Picasso.with(this).load(weatherObj.weather_icon1).into(mIvNightPic);
            if(!weatherObj.weather.contains("turn")){
                mTvDayWeather.setText(weatherObj.weather);
                mTvNightWeather.setText(weatherObj.weather);
            }else{
                String weather[] = weatherObj.weather.split("turn");
                mTvDayWeather.setText(weather[0]);
                mTvNightWeather.setText(weather[1]);
            }
            mTvWind.setText(weatherObj.wind + "\n" + weatherObj.winp);
            mViewTrend.setLowTemArray(lowTemArray)
                    .setHighTemArray(highTemArray)
                    .setType(i == 0 ? 0 : i == weatherList.size() - 1 ? 1 : 2);
            ll.addView(itemView);
        }
        mHsTrend.addView(ll);

weatherList is a collection of future weather data

Posted by rmmo on Tue, 26 Mar 2019 12:30:29 -0700