Java 纯代码创建安卓页面布局(含异步加载图片)

开发环境

  • macOS Sierra (10.12)
  • Android Studio 2.2 (minSDK 5.1)

总体效果

总体效果图

说明:
本示例采用的是Fragment实现的
列表中的分割线,是采用的 LinearLayout 自带的 divider 实现的

界面结构(Layout文件)

在 Fragment 中,采用 ScrollView + LinearLayout 实现,代码如下所示:

<ScrollView
    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"    
    android:scrollbars="vertical"    
    tools:context=".Fragment.HomeFrg">    
    <LinearLayout        
        android:id="@+id/frg_home"        
        android:layout_width="match_parent"        
        android:layout_height="match_parent"        
        android:orientation="vertical"        
        android:divider="@drawable/sep_home"        
        android:showDividers="middle" />
</ScrollView>

divider 的实现详见上述代码中,LinearLayout 的最后两行的属性声明。
文件目录如下:


divider 文件目录

sep_home.xml 文档结构如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/colorBG" />    
    <size android:height="10dp" />
</shape>

背景颜色定义在 values/colors 中,如下所示:

<color name="colorBG">#EEEEEE</color>

说明:divider 采用 Drawable 的 shape 来实现,在 shape 中,一定要添加 solid 和 size 两个元素,即使是透明背景色,也要添加这两个元素。并且,颜色的设置也要明文声明。

至此,界面布局的 xml 文件就准备完毕了,接下来开始些功能实现。

代码结构(Java 文件)

关于加载图片,在现在(新版本)的安卓开发过程中,如果在主线程直接加载网络图片,会报 NetworkOnMainThreadException 异常。
所以,这里采用了异步(结合线程池)加载的方式来进行。
当然,也可以采用成熟的第三方组件,如:Picasso(主页Github) 等。
本文自行实现,先创建一个自定义的图像类,如下所示:

import android.graphics.drawable.Drawable;
import android.os.Handler;

import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestImage {
    // 为了加快速度,在内存中开启缓存
    // 主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动
    public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();

    // 固定 10 个线程来执行任务
    private ExecutorService _exeService = Executors.newFixedThreadPool(10);

    private final Handler _handler = new Handler();

    public Drawable getImage(final String url, final Callback callback) {

        // 缓存中存在就用缓存中的图片
        if (imageCache.containsKey(url)) {
            SoftReference<Drawable> softReference = imageCache.get(url);

            if (softReference.get() != null) {
                return softReference.get();
            }
        }

        // 缓存中没有图片,就从网络中获取图片,同时,存入缓存
        _exeService.submit(new Runnable() {
            @Override
            public void run() {
                final Drawable drawable = getImage(url);
                imageCache.put(url, new SoftReference<Drawable>(drawable));

                _handler.post(new Runnable() {
                    @Override
                    public void run() {
                        callback.imageLoaded(drawable);
                    }
                });
            }
        });

        return null;
    }

    // 从网络中获取图片
    protected Drawable getImage(String url) {
        Drawable drawable = null;

        try {
            drawable = Drawable.createFromStream(new URL(url).openStream(), "img.png");
        } catch (Exception e) {
            e.printStackTrace();
        }

        return drawable;
    }

    // 回调接口
    public interface Callback {
        void imageLoaded(Drawable drawable);
    }
}

特别说明:用 final 参数的原因是:防止方法参数在调用时被篡改

图像类建立好了之后,就可以在主类中调用了。
接下来,就是在主程序中调用了。
同时,在主程序(Fragment)中,采用动态创建各元素的方式来进行布局,全部代码如下:

public class HomeFrg extends Fragment {

    private LinearLayout _layout;
    //private TestImage _testImage = new TestImage();

    public HomeFrg() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.frg_home, container, false);
        initView(view);

        // Inflate the layout for this fragment
        return view;
    }

    public void setMenuVisibility(boolean menuVisible) {
        super.setMenuVisibility(menuVisible);

        if (getView() != null) {
            getView().setVisibility(menuVisible ? View.VISIBLE : View.GONE);
        }
    }

    private void initView(View view) {
        _layout = (LinearLayout) view.findViewById(R.id.frg_home);

        // TODO:从数据库获取数据

        // 这里直接循环 3 次来进行界面效果的展示
        for (int i = 0; i < 3; i++) {
            initCell(view);
        }
    }

    private void initCell(View view) {
        Context self = this.getContext();

        // 创建单元格(RelativeLayout)
        RelativeLayout.LayoutParams layoutWrapper = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        RelativeLayout wrapper = new RelativeLayout(self);
        wrapper.setBackgroundColor(Helper.getColor(self, R.color.colorWhite));
        wrapper.setPadding(0, 30, 0, 30);
        _layout.addView(wrapper, layoutWrapper);

        // 创建封面图片(ImageView)
        RelativeLayout.LayoutParams layoutCover = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 600);
        ImageView imgCover = new ImageView(self);
        int idCover = view.generateViewId();   // 为了依次分开排列各控件,需设定控件的 Id,这里用 view.generateViewId() 自动产生
        imgCover.setId(idCover);
        loadImage("http://pic9.nipic.com/20100904/4845745_195609329636_2.jpg", imgCover);   // 异步加载任意网络图片(用于测试)
        imgCover.setScaleType(ImageView.ScaleType.CENTER_CROP);
        imgCover.setPadding(20, 0, 20, 0);
        wrapper.addView(imgCover, layoutCover);

        // 创建标题(TextView)
        RelativeLayout.LayoutParams layoutTitle = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutTitle.setMargins(20, 0, 20, 0);
        layoutTitle.addRule(RelativeLayout.BELOW, idCover);   // 在上一个元素的下方进行呈现,否则,在界面中,两个元素会重叠在一起
        TextView txtTitle = new TextView(self);
        int idTitle = view.generateViewId();
        txtTitle.setId(idTitle);
        txtTitle.setText("标题内容标题内容标题内容标题内容标题内容标题内容");
        txtTitle.setTextSize(20);
        txtTitle.setEllipsize(TextUtils.TruncateAt.END);   // 末尾多余字符用省略号代替
        txtTitle.setSingleLine();   // 设置单行显示
        txtTitle.setTextColor(Helper.getColor(self, R.color.colorBlack));
        wrapper.addView(txtTitle, layoutTitle);

        // 创建作者(TextView)
        RelativeLayout.LayoutParams layoutAuthor = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutAuthor.setMargins(20, 0, 20, 0);
        layoutAuthor.addRule(RelativeLayout.BELOW, idTitle);
        TextView txtAuthor = new TextView(self);
        int idAuthor = view.generateViewId();
        txtAuthor.setId(idAuthor);
        txtAuthor.setText("作者名称");
        txtAuthor.setTextColor(Helper.getColor(self, R.color.colorBlack));
        wrapper.addView(txtAuthor, layoutAuthor);

        // 创建日期(TextView)
        RelativeLayout.LayoutParams layoutTime = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutTime.setMargins(20, 0, 20, 0);
        layoutTime.addRule(RelativeLayout.BELOW, idAuthor);
        TextView txtTime = new TextView(self);
        txtTime.setText("2016年9月22日 16:33");
        wrapper.addView(txtTime, layoutTime);
    }

    // 再次封装 TestImage 类,方便本页面进行调用
    private void loadImage(String url, final ImageView imageView) {
        Drawable imgCache = new TestImage().getImage(url, new TestImage.Callback() {
            @Override
            public void imageLoaded(Drawable drawable) {
                imageView.setImageDrawable(drawable);
            }
        });

        if (imgCache != null) {
            imageView.setImageDrawable(imgCache);
        }
    }
}

至此,所有 Java 代码实现完毕。

写在最后
在 RelativeLayout 的代码布局中,可通过 LayoutParams 的 setMargins 和控件自身的 setPadding 来进行各处留白距离的微调。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,401评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,562评论 18 399