1. 程式人生 > >守望先鋒app(1)

守望先鋒app(1)

卡片 -i scaletype save tin eight attr xtend 過程

這個app就是從守望先鋒的官網下載相關的圖片、文字、視頻然後展示出來。

第一個功能是英雄介紹,所以先分析一波官網的數據.守望先鋒的英雄數據的官方網站是http://ow.blizzard.cn/heroes/,

技術分享

這個頁面有英雄的頭像和名字,如果想要看英雄的詳細介紹點擊英雄框就行,點擊第一個英雄跳轉的網址是http://ow.blizzard.cn/heroes/doomfist,點擊第二個英雄跳轉的網址是http://ow.blizzard.cn/heroes/genji,所以如果以後想要得到英雄的具體介紹在這裏還需要得到英雄的id,比如這裏的doomfist、genji

然後右鍵查看網頁源代碼

技術分享

這裏可以看到英雄的名字和頭像圖片的地址,還有id,所以這樣就分析結束了。

然後是這個活動的布局xml:

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical">

      <android.support.v7.widget.RecyclerView
          android:id="@+id/recycler_view"
          android:layout_width="match_parent"
android:layout_height="wrap_content" /> </LinearLayout>

這裏使用RecyclerView來作為展示的列表

然後是RecyclerView的子項的布局

<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_margin="5dp" app:cardCornerRadius="4dp"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/hero_image_card" android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="center" android:layout_gravity="center_horizontal"/> <TextView android:id="@+id/hero_name_card" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="5dp" android:textSize="16sp"/> </LinearLayout> </android.support.v7.widget.CardView>

最外層是卡片式的布局然後是一個垂直分布的圖片和文字,對應著英雄頭像和名字,完成後的效果是這樣

技術分享

然後最重要的就是下載數據的邏輯了,這裏使用的是jsoup對html的解析,篩選出想要的文本和圖片視頻的地址。

這裏需要添加的庫有:

    compile ‘org.jsoup:jsoup:1.10.3‘
    compile ‘com.squareup.okhttp3:okhttp:3.9.0‘

使用okhttp來獲取網站的html代碼。

首先建立一個類,存放英雄信息

public class Hero {

    private String imageUrl;//英雄頭像圖片地址

    private String name;//英雄名字

    private String heroId;//英雄id



    public Hero(String imageUrl, String name, String heroId) {
        this.imageUrl = imageUrl;
        this.name = name;
        this.heroId = heroId;
    }

    public String getUrl() {
        return imageUrl;
    }

    public void setUrl(String url) {
        this.imageUrl = url;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getHeroId() {
        return heroId;
    }

    public void setHeroId(String heroId) {
        this.heroId = heroId;
    }
}

然後在活動中建立一個List用於存放所有英雄的信息

private List<Hero> heroList = new ArrayList<>();

然後新建一個類用於發起一條HTTP請求,傳入地址,並註冊一個回調來處理服務器響應,

public class HttpUtil {

    public static void sendOkHttpRequest(String address, okhttp3.Callback callback){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(address).build();
        client.newCall(request).enqueue(callback);
    }
}

在主活動中這樣使用這個類:

 1     private void initHeroes(){
 2         String Url = "http://ow.blizzard.cn/heroes/";
 3         HttpUtil.sendOkHttpRequest(Url, new Callback() {
 4             @Override
 5             public void onResponse(Call call, Response response) throws IOException {
 6                 final String responseText = response.body().string();
 7                 new Thread(new Runnable() {
 8                     @Override
 9                     public void run() {
10                         //這裏用於解析html代碼
11                     }
12                 }).start();
13             }
14 
15             @Override
16             public void onFailure(Call call, IOException e) {
17                 e.printStackTrace();
18                 runOnUiThread(new Runnable() {
19                     @Override
20                     public void run() {
21                         Toast.makeText(MainActivity.this, "數據獲取失敗", Toast.LENGTH_SHORT).show();
22                     }
23                 });
24             }
25         });
26     }

第二行建立一個字符串用於存放網站的地址,然後調用剛剛創建的HttpUtil的sendOkHttpRequest方法,傳入地址,然後通過實現okhttp3.Callback這個接口獲取到html代碼並進行解析工作

 final String responseText = response.body().string();

這是第六行,這個response對象就是服務器返回的數據,這樣的寫法就是將得到的數據轉換成字符串類型,存放在responseText中。

具體獲得的數據就是用瀏覽器打開該網站,右鍵查看網頁源代碼,這樣的數據轉換成了字符串存放在responseText中,下圖就是字符創的具體累人了,然後Jsoup的作用就是采用CSS或類似jquery 選擇器(selector)語法來處理HTML文檔中的數據。

技術分享

然後是具體的解析代碼:

                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Document doc = Jsoup.parse(responseText);//將String類型的html轉換為Document
                        Elements elements1 = doc.select(".portrait"); //讀取圖片url
                        Elements elements2 = doc.select(".portrait-title");//讀取英雄名字
                        Elements elements3 = doc.select(".hero-portrait-detailed");//讀取英雄id
                        for (int j = 0; j < elements1.size(); j++) {
                            Hero hero = new Hero(elements1.get(j).attr("src"), elements2.get(j).text(),elements3.get(j).attr("data-hero-id"));
                            heroList.add(hero);
                        }
...
                    }
                }).start();

首先是使用Jsoup的parse方法將字符串類型的html代碼轉換成Document類型,然後就是Document的select方法來解析數據。

技術分享

這是其中一個英雄的html代碼,

技術分享

圖中的這一段data-hero-id="doomfist"是英雄的id,因為class是hero-portrait-detailed,所以通過這個Elements elements3 = doc.select(".hero-portrait-detailed"),以及elements3.get(j).attr("data-hero-id"),就可以獲取到一個英雄的id,這裏的elements3獲取到的是所有英雄的數據,所以這裏使用一個循環以及get()方法得到某一個英雄的信息

技術分享

這一段是英雄頭像的地址,和上面一樣

技術分享

這裏的英雄名字稍微不一樣些,elements2.get(j).text()這樣就行。

整個活動的代碼是:

public class MainActivity extends AppCompatActivity {

    private List<Hero> heroList = new ArrayList<>();
    private Handler handler;
    private HeroSelectAdapter heroSelectAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        initHeroes();
        handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                if(msg.what == 1){
                    GridLayoutManager layoutManager = new GridLayoutManager(MainActivity.this,2);
                    recyclerView.setLayoutManager(layoutManager);
                    heroSelectAdapter = new HeroSelectAdapter(heroList);
                    recyclerView.setAdapter(heroSelectAdapter);
                }
            }
        };
    }

    //讀取數據
    private void initHeroes(){
        String Url = "http://ow.blizzard.cn/heroes/";
        HttpUtil.sendOkHttpRequest(Url, new Callback() {
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String responseText = response.body().string();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Document doc = Jsoup.parse(responseText);//將String類型的html轉換為Document
                        Elements elements1 = doc.select(".portrait"); //讀取圖片url
                        Elements elements2 = doc.select(".portrait-title");//讀取英雄名字
                        Elements elements3 = doc.select(".hero-portrait-detailed");//讀取英雄id
                        for (int j = 0; j < elements1.size(); j++) {
                            Hero hero = new Hero(elements1.get(j).attr("src"), elements2.get(j).text(),elements3.get(j).attr("data-hero-id"));
                            heroList.add(hero);
                        }
                        Message msg = new Message();
                        msg.what = 1;
                        handler.sendMessage(msg);
                    }
                }).start();
            }

            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity.this, "數據獲取失敗", Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });
    }
}

因為解析過程的代碼不能放在主線程中,所以這裏使用的Handle異步消息處理機制,解析數據結束後發送消息給主線程然後在更新RecyclerView

最後有一個RecyclerView的適配器的代碼

public class HeroSelectAdapter  extends RecyclerView.Adapter<HeroSelectAdapter.ViewHolder> {

    private Context mContext;

    private List<Hero> mHero;

    static class ViewHolder extends RecyclerView.ViewHolder{

        CardView cardView;
        ImageView heroImage;
        TextView heroName;

        public ViewHolder(View view){
            super(view);
            cardView = (CardView) view;
            heroImage = (ImageView) view.findViewById(R.id.hero_image_card);
            heroName = (TextView) view.findViewById(R.id.hero_name_card);
        }
    }

    public HeroSelectAdapter(List<Hero> HeroList){
        mHero = HeroList;
    }

    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if(mContext == null){
            mContext = parent.getContext();
        }
        View view = LayoutInflater.from(mContext).inflate(R.layout.hero_select,parent,false);
        final ViewHolder holder = new ViewHolder(view);
        holder.cardView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int position = holder.getAdapterPosition();
                Hero hero = mHero.get(position);
                Intent intent = new Intent(mContext,HeroDetails.class);
                intent.putExtra(HeroDetails.Hero_NAME, hero.getName());
                intent.putExtra(HeroDetails.Hero_ID, hero.getHeroId());
                mContext.startActivity(intent);
            }
        });
        return   holder;
    }
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        Hero hero = mHero.get(position);
        viewHolder.heroName.setText(hero.getName());
        Glide.with(mContext).load(hero.getUrl()).into(viewHolder.heroImage);
    }

    public int getItemCount() {
        return mHero.size();
    }
}

onCreateViewHolder這個方法中的代碼是處理RecyclerView的點擊事件,啟動下一個活動,傳遞的數據是英雄的名字和id.

onCreateViewHolder

守望先鋒app(1)