3

【木棉花】基于JAVA UI开发的小游戏——推箱子(下)-开源基础软件社区-51CTO.COM

 1 year ago
source link: https://ost.51cto.com/posts/20362
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

【木棉花】基于JAVA UI开发的小游戏——推箱子(下) 原创 精华

上文笔者向大家分享了推箱子小游戏基础功能的实现,本文将继续向大家介绍如何做UI界面美化,以及如何利用轻量级偏好数据库做数据的存储和读取。

UI界面美化

MainAbilitySlice

【木棉花】基于JAVA UI开发的小游戏——推箱子(下)-开源基础软件社区
我们可以看到,所有的界面都是采用无框全屏化设计,因此第一步是要修改config.json文件,打开文件,将代码做出如下修改:
    ......
 "launchType": "standard"
      }
    ],
    "metaData": {
      "customizeData": [
        {
          "name": "hwc-theme",
          "value": "androidhwext:style/Theme.Emui.Light.NoTitleBar",
          "extra": ""
        }
      ]
    }
  }
}

然后设计按钮样式,首先新建一个graphic文件:

【木棉花】基于JAVA UI开发的小游戏——推箱子(下)-开源基础软件社区
接着在里面添加美化代码:
<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners
        ohos:radius="120"/>
    <stroke
        ohos:width="3vp"
        ohos:color="#fbc02d"/>
    <solid
        ohos:color="#fff8e1"/>
</shape>

现在分析界面需求,其中带有“Pokemon”字样的是本地图片,因此我们需要的控件有四个按钮以及一张图片,布局采用DirectionalLayout即可,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Image
        ohos:layout_alignment="horizontal_center"
        ohos:width="350vp"
        ohos:height="100vp"
        ohos:image_src="$media:pokemon"
        ohos:scale_mode="zoom_center"
        ohos:bottom_margin="20vp"
        />
    <Button
        ohos:id="$+id:startBtn"
        ohos:width="250vp"
        ohos:height="50vp"
        ohos:background_element="$graphic:background_ability_main"
        ohos:text_color="#ffffff"
        ohos:text_weight="700"
        ohos:text_size="20vp"
        ohos:text="开始游戏"
        ohos:bottom_margin="20vp"
        />
    <Button
        ohos:id="$+id:recordBtn"
        ohos:width="250vp"
        ohos:height="50vp"
        ohos:background_element="$graphic:button"
        ohos:text_color="#fbc02d"
        ohos:text_weight="700"
        ohos:text_size="20vp"
        ohos:text="历史记录"
        ohos:bottom_margin="20vp"
        />
    <Button
        ohos:id="$+id:aboutBtn"
        ohos:width="250vp"
        ohos:height="50vp"
        ohos:background_element="$graphic:button"
        ohos:text_color="#fbc02d"
        ohos:text_weight="700"
        ohos:text_size="20vp"
        ohos:text="关于游戏"
        ohos:bottom_margin="20vp"
        />
    <Button
        ohos:id="$+id:exitBtn"
        ohos:width="250vp"
        ohos:height="50vp"
        ohos:text_color="#fbc02d"
        ohos:background_element="$graphic:button"
        ohos:text_weight="700"
        ohos:text_size="20vp"
        ohos:text="退出游戏"
        ohos:bottom_margin="20vp"
        />

</DirectionalLayout>

至此第一个界面就美化完成了。
SelectSlice

【木棉花】基于JAVA UI开发的小游戏——推箱子(下)-开源基础软件社区
这个界面的布局跟第一个界面大同小异,只是少了一个按钮,还有就是按钮的样式有点不同,因此需要再写一个graphic文件,方法同上,这里直接给出代码:
<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:shape="rectangle">
    <corners
        ohos:radius="120"/>
    <stroke
        ohos:width="3vp"
        ohos:color="#7cc473"/>
    <solid
        ohos:color="#e5f0e4"/>
</shape>

界面的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical"
    ohos:alignment="center">
    <Image
        ohos:width="361vp"
        ohos:height="70vp"
        ohos:scale_mode="zoom_center"
        ohos:layout_alignment="center"
        ohos:bottom_margin="30vp"
        ohos:image_src="$media:select"
        />
    <Button
        ohos:layout_alignment="center"
        ohos:id="$+id:firstBtn"
        ohos:width="300vp"
        ohos:height="60vp"
        ohos:background_element="$graphic:select"
        ohos:text_color="#7cc473"
        ohos:text_weight="700"
        ohos:text_size="30fp"
        ohos:text="第一关"
        ohos:bottom_margin="30vp"
        />
    <Button
        ohos:layout_alignment="center"
        ohos:id="$+id:secondBtn"
        ohos:width="300vp"
        ohos:height="60vp"
        ohos:background_element="$graphic:select"
        ohos:text_color="#7cc473"
        ohos:text_weight="700"
        ohos:text_size="30fp"
        ohos:text="第二关"
        ohos:bottom_margin="30vp"
        />
    <Button
        ohos:layout_alignment="center"
        ohos:id="$+id:thirdBtn"
        ohos:width="300vp"
        ohos:height="60vp"
        ohos:background_element="$graphic:select"
        ohos:text_color="#7cc473"
        ohos:text_weight="700"
        ohos:text_size="30fp"
        ohos:text="第三关"
        ohos:bottom_margin="20vp"
        />

</DirectionalLayout>

InitSlice

【木棉花】基于JAVA UI开发的小游戏——推箱子(下)-开源基础软件社区
在加载界面中,只是用到了一个播放gif的第三方组件,以及一张图片(文字图片)一个进度条组件,布局也使用最常规的DirectionalLayout即可实现。
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical">

    <com.bumptech.glide.load.resource.gif.drawableability.DraweeView
        ohos:layout_alignment="center"
        ohos:top_margin="100vp"
        ohos:id="$+id:draweeView"
        ohos:width="360vp"
        ohos:height="360vp"
        />
    <Image
        ohos:width="291vp"
        ohos:height="53vp"
        ohos:layout_alignment="center"
        ohos:bottom_margin="30vp"
        ohos:scale_mode="zoom_center"
        ohos:image_src="$media:Loading"
        />
    <ProgressBar
        ohos:layout_alignment="center"
        ohos:height="30vp"
        ohos:width="330vp"
        ohos:id="$+id:pb"
        ohos:max="100"
        ohos:min="0"
        ohos:progress_width="330vp"
        ohos:background_element="#edf1bb"
        ohos:progress_element="#f2c867"
        ohos:progress="0"
        />
</DirectionalLayout>

GameSlice

【木棉花】基于JAVA UI开发的小游戏——推箱子(下)-开源基础软件社区
游戏界面的UI就稍微复杂一点,需要用到嵌套,之前说过,地图类继承自布局,所以实际上地图也是一个组件,理解了这一点之后,再来看代码会容易理解很多。整体布局用了DirectionalLayout纵向布局,在里面有需要横向布局的,则添加DirectionalLayout的横向布局,做一个简单的嵌套。
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical"
    >

    <DirectionalLayout
        ohos:id="$+id:dl"
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:orientation="horizontal"
        ohos:bottom_margin="20vp"
        >
        <Button
            ohos:id="$+id:backBtn"
            ohos:top_margin="10vp"
            ohos:left_margin="10vp"
            ohos:right_margin="8vp"
            ohos:width="40vp"
            ohos:height="40vp"
            ohos:layout_alignment="left"
            ohos:background_element="$graphic:backbutton"
            />
        <Button
            ohos:id="$+id:setBtn"
            ohos:top_margin="10vp"
            ohos:left_margin="8vp"
            ohos:right_margin="10vp"
            ohos:width="40vp"
            ohos:height="40vp"
            ohos:background_element="$graphic:setbutton"
            />
        <Image
            ohos:layout_alignment="right"
            ohos:top_margin="10vp"
            ohos:left_margin="85vp"
            ohos:width="40vp"
            ohos:height="40vp"
            ohos:scale_mode="zoom_center"
            ohos:image_src="$media:clock"
            />

    </DirectionalLayout>

    <Image
        ohos:layout_alignment="center"
        ohos:width="372vp"
        ohos:height="35vp"
        ohos:scale_mode="zoom_center"
        ohos:image_src="$media:goal"
        ohos:bottom_margin="20vp"
        />

    <com.example.pokemon.slice.GameMap
        ohos:layout_alignment="center"
        ohos:id="$+id:gameMap1"
        ohos:width="match_content"
        ohos:height="match_content"
        ohos:bottom_margin="15vp"
        />
    <DirectionalLayout
        ohos:height="210vp"
        ohos:width="match_parent"
        ohos:orientation="horizontal"
        ohos:top_margin="20vp">
        <Image
            ohos:layout_alignment="center"
            ohos:width="158vp"
            ohos:height="170vp"
            ohos:scale_mode="zoom_center"
            ohos:image_src="$media:stack"
            ohos:bottom_margin="20vp"
            />
        <Button
            ohos:id="$+id:stackBtn"
            ohos:left_margin="15vp"
            ohos:bottom_margin="10vp"
            ohos:width="150vp"
            ohos:height="150vp"
            ohos:background_element="$graphic:button"
            ohos:text="回退"
            ohos:text_size="50fp"
            ohos:text_color="#fbc02d"
            />
    </DirectionalLayout>

</DirectionalLayout>

四个界面美化完毕!接下来做一些细节的调整。在按下历史记录按钮时,会显示每个关卡最近的一次历史记录,效果如下:

【木棉花】基于JAVA UI开发的小游戏——推箱子(下)-开源基础软件社区
这实际上是一个自定义样式的CommonDialog,如何自定义?首先创建一个自定义的RecordDialog类和美化用的xml文件,然后在类里面添加自己的xml文件,具体方法可以看代码:
public class RecordDialog {
    static CommonDialog commonDialog;

    static void showDialog(Context context,String s1,String s2,String s3){
        DirectionalLayout dl = (DirectionalLayout) LayoutScatter.getInstance(context)
                .parse(ResourceTable.Layout_recordlayout,null,false);
        commonDialog = new CommonDialog(context);
        commonDialog.setAutoClosable(true);

        Button Btn = (Button) dl.findComponentById(ResourceTable.Id_Btn);

        Text first = (Text) dl.findComponentById(ResourceTable.Id_firstText);
        first.setText(s1);

        Text second = (Text) dl.findComponentById(ResourceTable.Id_secondText);
        second.setText(s2);

        Text third = (Text) dl.findComponentById(ResourceTable.Id_thirdText);
        third.setText(s3);

        Btn.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                commonDialog.destroy();
            }
        });
        commonDialog.setCornerRadius(15);
        commonDialog.setContentCustomComponent(dl).show();
    }
}

xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:orientation="vertical">
    <Text
        ohos:left_margin="5vp"
        ohos:right_margin="5vp"
        ohos:layout_alignment="left"
        ohos:width="match_content"
        ohos:height="match_content"
        ohos:text="最新通关记录:"
        ohos:multiple_lines="true"
        ohos:top_margin="20vp"
        ohos:text_size="20fp"
        />
    <Text
        ohos:id="$+id:firstText"
        ohos:left_margin="120vp"
        ohos:right_margin="5vp"
        ohos:layout_alignment="left"
        ohos:width="match_content"
        ohos:height="match_content"
        ohos:text="第一关:无"
        ohos:multiple_lines="true"
        ohos:top_margin="10vp"
        ohos:text_size="20fp"
        />
    <Text
        ohos:left_margin="120vp"
        ohos:right_margin="5vp"
        ohos:layout_alignment="left"
        ohos:id="$+id:secondText"
        ohos:width="match_content"
        ohos:height="match_content"
        ohos:text="第二关:无"
        ohos:multiple_lines="true"
        ohos:top_margin="10vp"
        ohos:text_size="20fp"
        />
    <Text
        ohos:left_margin="120vp"
        ohos:right_margin="5vp"
        ohos:layout_alignment="left"
        ohos:id="$+id:thirdText"
        ohos:width="match_content"
        ohos:height="match_content"
        ohos:text="第三关:无"
        ohos:multiple_lines="true"
        ohos:top_margin="10vp"
        ohos:text_size="20fp"
        />

    <Button
        ohos:id="$+id:okBtn"
        ohos:width="250vp"
        ohos:height="50vp"
        ohos:background_element="$graphic:background_ability_main"
        ohos:text_color="#ffffff"
        ohos:text_weight="700"
        ohos:text_size="20vp"
        ohos:top_margin="30vp"
        ohos:text="确定"
        ohos:bottom_margin="20vp"
        ohos:left_margin="40vp"
        />
</DirectionalLayout>

关于这样的设计,这个小游戏中还有一处,点击关于游戏弹出的界面同样也是这么实现的:

【木棉花】基于JAVA UI开发的小游戏——推箱子(下)-开源基础软件社区
代码如下:
public class MyDialog {
    private static Text version;
    static void showDialog(Context context){
        DirectionalLayout dl = (DirectionalLayout) LayoutScatter.getInstance(context)
                .parse(ResourceTable.Layout_mydialoglayout,null,false);
        CommonDialog commonDialog = new CommonDialog(context);
        commonDialog.setAutoClosable(true);

        Button knowBtn = (Button) dl.findComponentById(ResourceTable.Id_knowBtn);



        knowBtn.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                commonDialog.destroy();
            }
        });



        commonDialog.setCornerRadius(15);
        commonDialog.setContentCustomComponent(dl).show();
    }
    static String getVersion(){
        return version.getText();
    }
}

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:orientation="vertical"
    ohos:alignment="center">
    <Text
        ohos:left_margin="5vp"
        ohos:right_margin="5vp"
        ohos:layout_alignment="left"
        ohos:width="match_content"
        ohos:height="match_content"
        ohos:text="游戏目标:收服在场的所有宝可梦!"
        ohos:multiple_lines="true"
        ohos:top_margin="20vp"
        ohos:text_size="20fp"
        />
    <Text
        ohos:left_margin="5vp"
        ohos:right_margin="5vp"
        ohos:layout_alignment="left"
        ohos:width="match_content"
        ohos:height="match_content"
        ohos:text="游戏玩法:通过滑动屏幕控制人物,推动精灵球收服宝可梦,当收服完所有宝可梦时获得游戏胜利!"
        ohos:multiple_lines="true"
        ohos:top_margin="10vp"
        ohos:text_size="20fp"
        />
    <Text
        ohos:left_margin="5vp"
        ohos:right_margin="5vp"
        ohos:layout_alignment="left"
        ohos:id="$+id:versionText"
        ohos:width="match_content"
        ohos:height="match_content"
        ohos:text="游戏版本号:V1.0.0"
        ohos:multiple_lines="true"
        ohos:top_margin="10vp"
        ohos:text_size="20fp"
        />
    <Text
        ohos:left_margin="5vp"
        ohos:right_margin="5vp"
        ohos:layout_alignment="left"
        ohos:id="$+id:showText"
        ohos:width="match_content"
        ohos:height="match_content"
        ohos:text="开发人员:木棉花蓝锐鑫"
        ohos:multiple_lines="true"
        ohos:top_margin="10vp"
        ohos:text_size="20fp"
        />

    <Button
        ohos:id="$+id:knowBtn"
        ohos:width="250vp"
        ohos:height="50vp"
        ohos:background_element="$graphic:background_ability_main"
        ohos:text_color="#ffffff"
        ohos:text_weight="700"
        ohos:text_size="20vp"
        ohos:top_margin="30vp"
        ohos:text="已了解"
        ohos:bottom_margin="20vp"
        />
</DirectionalLayout>

游戏中最后一处UI设计,就是点击设置按钮时出现的一个滑动块组件,可以保存一些全局设置:

【木棉花】基于JAVA UI开发的小游戏——推箱子(下)-开源基础软件社区
public class SetDialog {
    static void showDialog(Context context){
        DirectionalLayout dl = (DirectionalLayout) LayoutScatter.getInstance(context)
                .parse(ResourceTable.Layout_setlayout,null,false);
        CommonDialog commonDialog = new CommonDialog(context);
        commonDialog.setAutoClosable(true);

        Button sureBtn = (Button) dl.findComponentById(ResourceTable.Id_sureBtn);
        Switch choose = (Switch) dl.findComponentById(ResourceTable.Id_choose);

        String value = MyDB.getString(dl.getContext(),"save");
        if(value != null){
            if(value.compareTo("开") == 0){
                choose.setChecked(true);
            }
            else if(value.compareTo("关") == 0){
                choose.setChecked(false);
            }
        }

        choose.setCheckedStateChangedListener(new AbsButton.CheckedStateChangedListener() {
            @Override
            public void onCheckedChanged(AbsButton absButton, boolean b) {
                String key = "save";
                if(b){
                    MyDB.putString(dl.getContext(),key,"开");
                }
                else {
                    MyDB.putString(dl.getContext(), key,"关");
                }
            }
        });


        sureBtn.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                commonDialog.destroy();
            }
        });

        commonDialog.setCornerRadius(15);
        commonDialog.setContentCustomComponent(dl).show();
    }
}
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_content"
    ohos:width="match_content"
    ohos:orientation="vertical">

    <DirectionalLayout
        ohos:id="$+id:dl"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:orientation="horizontal"
        >
        <Text
            ohos:left_margin="70vp"
            ohos:right_margin="25vp"
            ohos:layout_alignment="left"
            ohos:id="$+id:thirdText"
            ohos:width="match_content"
            ohos:height="match_content"
            ohos:text="自动保存 "
            ohos:multiple_lines="true"
            ohos:top_margin="10vp"
            ohos:text_size="30fp"
            />
        <Switch
            ohos:id="$+id:choose"
            ohos:height="40vp"
            ohos:width="100vp"
            ohos:top_margin="10vp"
            ohos:text_state_on="开"
            ohos:text_state_off="关"
            ohos:text_color="#ffffff"
            ohos:text_size="20fp"
            ohos:thumb_element="#eff1f8"
            ohos:track_element="#f9bf2d"
            />

        </DirectionalLayout>


    <Button
        ohos:id="$+id:sureBtn"
        ohos:width="250vp"
        ohos:height="50vp"
        ohos:background_element="$graphic:background_ability_main"
        ohos:text_color="#ffffff"
        ohos:text_weight="700"
        ohos:text_size="20vp"
        ohos:top_margin="30vp"
        ohos:text="确定"
        ohos:bottom_margin="20vp"
        ohos:left_margin="40vp"
        />

</DirectionalLayout>

至此,UI美化部分已经全部完成。

这里用到轻量级偏好数据库,关于数据库怎么使用,可以看这篇文章,文章写得很详细!
利用数据库存储每个关卡的信息,首先要新建一个数据库类MyDB:

public class MyDB {
    private static final String PREFERENCE_FILE_NAME = "DB";
    private static Preferences preferences;
    private static DatabaseHelper databaseHelper;
    private static Preferences.PreferencesObserver mPreferencesObserver;

    private static void initPreference(Context context){
        if(databaseHelper==null){
            databaseHelper = new DatabaseHelper(context);
        }
        if(preferences==null){
            preferences = databaseHelper.getPreferences(PREFERENCE_FILE_NAME);
        }

    }

    public static void putString(Context context, String key, String value) {
        initPreference(context);
        preferences.putString(key, value);
        preferences.flush();
    }

    public static String getString(Context context, String key) {
        initPreference(context);
        return preferences.getString(key, null);
    }

    public static boolean deletePreferences(Context context) {
        initPreference(context);
        boolean isDelete= databaseHelper.deletePreferences(PREFERENCE_FILE_NAME);
        return isDelete;
    }

    public static void registerObserver(Context context, Preferences.PreferencesObserver preferencesObserver){
        initPreference(context);
        mPreferencesObserver=preferencesObserver;
        preferences.registerObserver(mPreferencesObserver);
    }

    public static void unregisterObserver(){
        if(mPreferencesObserver!=null){
            // 向preferences实例注销观察者
            preferences.unregisterObserver(mPreferencesObserver);
        }
    }
}

在结束游戏时,如果打开了自动保存按钮,则进行存储:

                        if(gameMap.isWin()){
                            tickTimer.stop();
                            CommonDialog commonDialog = new CommonDialog(getContext());
                            commonDialog.setSize(800,400);
                            commonDialog.setTitleText("  注意");
                            commonDialog.setContentText("             恭喜您完成游戏!!!");
                            commonDialog.setButton(0, "确定", new IDialog.ClickedListener() {
                                @Override
                                public void onClick(IDialog iDialog, int i) {
                                    commonDialog.destroy();
                                    String value = MyDB.getString(getContext(),"save");
                                    if(value != null){
                                        if(value.compareTo("开") == 0){
                                            MyDB.putString(getContext(),key,tickTimer.getText());
                                        }
                                    }
                                    present(new SelectSlice(),new Intent());
                                    terminate();
                                }
                            });
                            commonDialog.show();


                        }

在点击历史记录时,会进行数据读取:

        //历史记录按钮
        recordBtn.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                String[] s = {"第一关:无","第二关:无","第三关:无"};
                String first = MyDB.getString(getContext(),"first");
                String second = MyDB.getString(getContext(),"second");
                String third = MyDB.getString(getContext(),"third");

                if(first == null){
                    first = s[0];
                }
                else {
                    first = "第一关:" + first;
                }
                if(second == null){
                    second = s[1];
                }
                else {
                    second = "第二关:" + second;
                }
                if(third == null){
                    third = s[2];
                }
                else {
                    third = "第三关:" + third;
                }

                RecordDialog.showDialog(getContext(),first,second,third);
            }
        });

开启自动保存,才会在游戏结束时存进数据库,实际上也是利用数据库中某个key中的value控制,具体实现如下:

        choose.setCheckedStateChangedListener(new AbsButton.CheckedStateChangedListener() {
            @Override
            public void onCheckedChanged(AbsButton absButton, boolean b) {
                String key = "save";
                if(b){
                    MyDB.putString(dl.getContext(),key,"开");
                }
                else {
                    MyDB.putString(dl.getContext(), key,"关");
                }
            }
        });

至此,项目已经全部分享完成,由于作品中涉及大量的图片资源均是网络资源(避免侵权),故仅作学习交流使用,实际上,绝大部分代码已经在文章中了,剩下的就是读者理解之后动手衔接起来!一定要动手!
后续作者也会开发更多的小游戏供大家学习交流~(下期可能就是ArkUI的小游戏啦!)期待与大家一起进步!!!

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
Pokemon.zip 10.9M 8次下载
已于2022-12-30 16:45:20修改

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK