美文网首页
angular自定义拖拽布局功能,实现简单低代码交互

angular自定义拖拽布局功能,实现简单低代码交互

作者: 甘道夫老矣 | 来源:发表于2023-03-15 15:50 被阅读0次

有些插件实现拖拽布局不是很流畅,这里直接简单实现通过左侧菜单组件,拖入到画板上实现样式,右侧可根据数据交互实现联动,也是参考了部分网上教程,后期根据自己思路修改后的结果,细看代码后,其实思路还是那一套,不停的套娃套出来的。

简单实现效果图就是 如下


image.png

1.左侧组件区域

//html
<div class="tool-list">
    <ul>
        <li *ngFor="let item of componentList" class="list-box" #dragPlaceholderCanvas appDrag
            [componentType]="item.type" [dragPlaceholder]="dragPlaceholderCanvas">
            <div class="label"> {{item.label}}</div>
            <button color="primary">
                拖拽
            </button>
        </li>
    </ul>
</div>
//css
.tool-list {
    border: 1px solid #333333;
    width: 200px;
    box-sizing: border-box;
    padding: 10px;
    border-radius: 4px;
    overflow: hidden;
    flex-basis: 200px;
    height: 100%;
    ul {
        margin: 0;
        padding: 0;
        list-style: none;

        li {
            padding: 10px 0;
            display: flex;
            justify-content: space-between;
            align-items: center;
            width: 100%;

            .label {
                font-size: 18px;
            }
        }

        .list-box {
            border: 1px solid #ccc;
        }
    }
}
// ts
import { Component ,OnInit} from '@angular/core';

@Component({
  selector: 'app-tool-list',
  templateUrl: './tool-list.component.html',
  styleUrls: ['./tool-list.component.scss']
})
export class ToolListComponent implements OnInit {
    componentList = [
        {label: '文本', type: 'text'},
        {label: '列表', type: 'list'},
        {label: '输入框', type: 'input'},
        {label: '布局', type: 'layout'},
    ];

    constructor() {
    }
    ngOnInit(): void {
    }
}

2.右侧联动区域,这里只做了简单的交互模式,其实思路还是那样,数据绑定的逻辑,处理的是同一份json数据

//html
<div class="setting-box">
    <div class="from-box" *ngIf="drawOrActiveItem">
        <nz-divider nzPlain [nzText]="'组件名'+drawOrActiveItem.componentName"></nz-divider>
        <div nz-row>
            <div nz-col nzSpan="6">表单栅格</div>
            <div nz-col nzSpan="18">
                <nz-slider [(ngModel)]="drawOrActiveItem.styles.grid" [nzMax]="24"[nzMin]="1" ></nz-slider>
            </div>
        </div>

        <div nz-row *ngIf="drawOrActiveItem.type==='layout'">
            <div nz-col nzSpan="6">布局模式</div>
            <div nz-col nzSpan="18">
                <nz-radio-group [(ngModel)]="drawOrActiveItem.styles.type" nzButtonStyle="solid">
                    <label nz-radio-button nzValue="default">default</label>
                    <label nz-radio-button nzValue="flex">flex</label>
                </nz-radio-group>
            </div>
        </div>

        <div nz-row *ngIf="drawOrActiveItem.styles.type==='flex'">
            <div nz-col nzSpan="6">水平排列</div>
            <div nz-col nzSpan="18">
                <nz-select [(ngModel)]="drawOrActiveItem.styles.justify" style="width: 100%;">
                    <nz-option nzValue="start" nzLabel="左侧"></nz-option>
                    <nz-option nzValue="end" nzLabel="右侧"></nz-option>
                    <nz-option nzValue="center" nzLabel="居中"></nz-option>
                    <nz-option nzValue="space-around" nzLabel="平均"></nz-option>
                    <nz-option nzValue="space-between" nzLabel="两端"></nz-option>
                </nz-select>
            </div>
        </div>

        <div nz-row *ngIf="drawOrActiveItem.type==='input'">
            <div nz-col nzSpan="6">lable宽度</div>
            <div nz-col nzSpan="18">
                <nz-input-number [(ngModel)]="drawOrActiveItem.styles.layout.labelWidth" [nzMin]="1"  [nzStep]="1" style="width: 100%;"></nz-input-number> 
            </div>
        </div>

    </div>
</div>


//css
.setting-box{
    border: 1px solid #333333;
    width: 350px;
    box-sizing: border-box;
    padding: 10px;
    border-radius: 4px;
    overflow: hidden;
    flex-basis: 200px;
    height: 100%;

    .from-box{
        width: 100%;
        height: 100%;
    }
}


//ts
import { AfterViewInit, Component, OnInit, Input, Output, EventEmitter, SimpleChanges } from '@angular/core';
import { OnChangeType } from 'ng-zorro-antd/core/types';
@Component({
    selector: 'app-setting-module',
    templateUrl: './setting-module.component.html',
    styleUrls: ['./setting-module.component.scss']
})
export class SettingModuleComponent implements OnInit {
    @Input() drawOrActiveItem: any;

    constructor() {
    }

    ngOnInit(): void {
    }

    ngOnChanges(): void {
        console.log(this.drawOrActiveItem);
    }

}

3.画板区域,也是核心交互的部分,这里一开始是通过很复杂的套娃实现后,发现代码可以简化,从200行的html套娃,简化成了100多行,以及在拖拽过程中,拖和放下两个关键动作的数据处理。

//html
<div class="container">
    <div class="drawing-board" appDrop (dropEvent)="getDropEventData($event)" [dropData]="defaultComponentInfo">
        <ng-container
            *ngTemplateOutlet="colTpl; context: {rows: defaultComponentInfo.component,rowId:defaultComponentInfo.id}">
        </ng-container>
    </div>
    <ng-container *ngIf="defaultComponentInfo.component.length===0">
        <div class="empty-info">
            从左侧拖入或点选组件进行表单设计
        </div>
    </ng-container>
</div>

<!-- 套娃流式布局base -->
<ng-template #childTemplate let-rowItem="rows">
    <div class="draw-col" [ngStyle]="{'width': 'calc(100% / ('+ (24/rowItem.styles.grid)+'))'}">
        <div nz-row class="drawing-row-item" [ngClass]="{'active-drawing-row-item ':activeId===rowItem.id}"
            (click)="handleActive($event,rowItem)" #dragPlaceholderCanvas appDrag [componentType]="rowItem.type"
            [componentId]="rowItem.id" [dragPlaceholder]="dragPlaceholderCanvas"
            (drawEndEvent)="getDrawEndEventData($event)" (drawStartEvent)="getDrawStartEventData($event)">

            <span class="component-name">{{rowItem.componentName}}</span>
            <ng-container *ngTemplateOutlet="dropArea; context: {rows: rowItem}"></ng-container>
            <!-- 删除按钮组件 -->
            <ng-container *ngTemplateOutlet="itemBtn; context: {rows: rowItem}"></ng-container>
        </div>
    </div>
</ng-template>

<!-- 拖拽内容 -->
<ng-template #dropArea let-rows="rows">
    <div class="drag-wrapper" appDrop (dropEvent)="getDropEventData($event)" [dropData]="rows">
        <ng-container *ngIf="rows.styles.type==='flex'">
            <ng-container *ngIf=" rows.children && rows.children.length>0">
                <div nz-row [nzJustify]="rows.styles.justify" class="drag-wrapper-row">
                    <ng-container *ngTemplateOutlet="colTpl;context: {rows:rows.children,rowId: rows.id}">
                    </ng-container>
                </div>
            </ng-container>
        </ng-container>

        <ng-container *ngIf="rows.styles.type==='default'">
            <ng-container *ngIf=" rows.children && rows.children.length>0">
                <ng-container *ngTemplateOutlet="colTpl;context: {rows:rows.children,rowId: rows.id}">
                </ng-container>
            </ng-container>
        </ng-container>
    </div>
</ng-template>


<!-- 列模块 -->
<ng-template #colTpl let-rows="rows" let-rowId="rowId">
    <ng-container *ngFor="let row of rows">
        <!-- 布局类型 -->
        <ng-container *ngIf="row.type==='layout' ">
            <ng-container *ngTemplateOutlet="childTemplate; context: {rows: row}"></ng-container>
        </ng-container>

        <!-- input类型 -->
        <ng-container *ngIf="row.type==='input' ">
            <div class="draw-col drawing-input-item" [ngClass]="{'active-drawing-row-item ':activeId===row.id}"
                [ngStyle]="{'width': 'calc(100% / ('+ (24/row.styles.layout.grid)+'))'}" #dragPlaceholderCanvas appDrag
                [componentType]="row.type" [componentId]="row.id" [dragPlaceholder]="dragPlaceholderCanvas"
                (drawEndEvent)="getDrawEndEventData($event)" (drawStartEvent)="getDrawStartEventData($event)"
                (click)="handleActive($event,row)">
                <div class="input-item">
                    <div class="input-item-lable" [ngStyle]="{'width':row.styles.layout.labelWidth+'px'}">{{row.label}}
                    </div>
                    <div class="input-item-input" >
                        <input nz-input [(ngModel)]="row.dataBinding.fieldName" />
                    </div>
                </div>
                <!-- 删除按钮组件 -->
                <ng-container *ngTemplateOutlet="itemBtn; context: {rows: row}"></ng-container>
            </div>
        </ng-container>
    </ng-container>
</ng-template>


<!-- 删除组件 -->
<ng-template #itemBtn let-rows="rows">
    <span class="drawing-item-delete" title="删除" (click)="handleDelete($event,rows)">
        <span nz-icon nzType="delete" nzTheme="outline"></span>
    </span>
</ng-template>

//css
.container {
    width: 100%;
    height: 100vh;
    position: relative;

    .drawing-board {
        height: 100%;
        position: relative;

        .active-drawing-row-item {
            border: 1px dashed #409EFF;

            // 只有点击选中了 才会有删除按钮,必须加&,必须是邻父子关系,否则嵌套里面都会显示
            &>.drawing-item-delete {
                display: initial;
            }

            &>.input-item {
                background: #f6f7ff;
                border-radius: 6px;
            }
        }

        z-index: 1;
    }

    .draw-col {
        // display: inline-block;
        float: left;
        padding-left: 5px;
        padding-right: 5px;


    }

    .drawing-row-item {
        position: relative;
        cursor: move;
        -webkit-box-sizing: border-box;
        box-sizing: border-box;
        border: 1px dashed #ccc;
        border-radius: 3px;
        padding: 0 2px;
        margin-bottom: 15px;

        &::after,
        &::before {
            display: table;

        }

        .draw-col {
            margin-top: 20px;
        }

        // 套娃中的样式当前样式
        .drawing-row-item {
            margin-bottom: 2px;
        }


        // 文字样式
        .component-name {
            position: absolute;
            top: 0;
            left: 0;
            font-size: 12px;
            color: #bbb;
            display: inline-block;
            padding: 0 6px;
        }

        // 可draw位置的样式
        .drag-wrapper {
            min-height: 80px;
            width: 100%;
        }

        // btn按钮样式
        .drawing-item-delete {
            display: none;
            position: absolute;
            top: -10px;
            width: 22px;
            height: 22px;
            line-height: 20px;
            text-align: center;
            border-radius: 50%;
            font-size: 12px;
            border: 1px solid;
            cursor: pointer;
            z-index: 1;
            right: 24px;
            border-color: #F56C6C;
            color: #F56C6C;
            background: #fff;
        }
    }

    .drawing-input-item {
        position: relative;
        cursor: move;
        -webkit-box-sizing: border-box;
        box-sizing: border-box;
        border: 1px dashed #ccc;
        border-radius: 3px;
        padding: 0 2px;
        margin-bottom: 15px;

        &::after,
        &::before {
            display: table;
        }

        .input-item {
            width: 100%;
            border-radius: 6px;
            padding: 12px 10px;
            margin-bottom: 2px;
            display: flex;
            align-items: center;
            // 文本框设计层暂时不能编辑
            pointer-events: none;
            &::after,
            &::before {
                display: table;
                content: "";
            }
        }

        // btn按钮样式
        .drawing-item-delete {
            display: none;
            position: absolute;
            top: -10px;
            width: 22px;
            height: 22px;
            line-height: 20px;
            text-align: center;
            border-radius: 50%;
            font-size: 12px;
            border: 1px solid;
            cursor: pointer;
            z-index: 1;
            right: 24px;
            border-color: #F56C6C;
            color: #F56C6C;
            background: #fff;
        }
    }

    .empty-info {
        position: absolute;
        top: 46%;
        left: 0;
        right: 0;
        text-align: center;
        font-size: 18px;
        color: #ccb1ea;
        letter-spacing: 4px;
        z-index: 0;
    }
}

//ts
import { AfterViewInit, Component, OnInit, Output, EventEmitter } from '@angular/core';

import { getIdGlobal } from '../../../../utils/db';
import { layoutUiComponents, inputUiComponents } from "../../../../utils/config";

@Component({
    selector: 'app-draw-area',
    templateUrl: './draw-area.component.html',
    styleUrls: ['./draw-area.component.scss']
})
export class DrawAreaComponent implements OnInit, AfterViewInit {

    @Output() checkedEvent: EventEmitter<any> = new EventEmitter();
    defaultComponentInfo: any = {
      //随机生成一个id
        id: getIdGlobal(),
        component: [],
        style: {},

    };
    // 拽入的item
    dropItem: any;
    // 托的item或者点击选中的item
    drawOrActiveItem: any;
    // 记录一个id参数
    idGlobal = getIdGlobal();
    // 当前点击选中的id
    activeId: any = getIdGlobal();
    // 拖拽的模块的id
    drawId: any;
    constructor() {
    }
    ngOnInit(): void {
    }

    ngAfterViewInit() {
    }

    // 拽放下
    getDropEventData(data: any): any {
        // console.log(data);
        // 如果从左侧组件拖入到画板中
        if (!this.drawId) {
            let config: any;
            if (data.layout === 'layout') {
                // 每次赋值都是最新的组件样式
                config = JSON.parse(JSON.stringify(layoutUiComponents));
            } else if (data.layout === 'input') {
                // 每次赋值都是最新的组件样式
                config = JSON.parse(JSON.stringify(inputUiComponents));
            } else {
                console.log('待研发');
                return;
            }

            this.idGlobal = ++this.idGlobal;
            if (config) {
                config.id = this.idGlobal;
                config.renderKey = `${config.id}${+new Date()}`;// 改变renderKey后可以实现强制更新组件
                config.componentName = `row${this.idGlobal}`;
                this.activeId = config.id;

                if (!Array.isArray(config.children)) {
                    config.children = [];
                }
                // console.log(config);
                if (data.currentAreaInfo.component) {
                    data.currentAreaInfo.component.push(config);
                } else {
                    data.currentAreaInfo.children.push(config);
                }
                //TODO 这里最后停下的数据就是可以操作的数据
                this.checkedEvent.emit(config)
            }
        } else {
            this.dropItem = data;
        }
        // console.log(this.defaultComponentInfo);
    }

    // 从画布中-拖开始
    getDrawStartEventData(data: any): void {
        this.drawId = data.id;
    }

    // 从画布中-拖结束
    getDrawEndEventData(data: any): void {
        const activeItem = this.filterData(this.drawId, this.defaultComponentInfo.component);
        this.drawOrActiveItem = activeItem;
        // console.log(this.drawOrActiveItem)
        // console.log(this.dropItem);

        // 这里需要考虑,如果拖的地方没有拽接收
        if (this.dropItem) {
            if (!this.dropItem.currentAreaInfo.component) {
                // 如果拖的块和放的块都是同一个,那么就放那里0101B350.png,不做交互
                if (activeItem.id !== this.dropItem.currentAreaInfo.id) {
                    // 去掉被拖的
                    this.loopfilter(this.defaultComponentInfo.component, data.id);
                    //   push拖的到新的位置
                    this.setDropData();
                }
            } else {
                // 如果从里面托到最外层
                // 去掉被拖的
                this.loopfilter(this.defaultComponentInfo.component, data.id);
                //   push拖的到新的位置
                this.setDropData();
            }
        } else {
            this.drawId = null;
            this.dropItem = null;
        }

    }

    // 拽结束
    setDropData(): void {
        if (this.drawOrActiveItem) {
            this.activeId = this.drawOrActiveItem.id;
            if (this.dropItem.currentAreaInfo.component) {
                this.dropItem.currentAreaInfo.component.push(this.drawOrActiveItem);
            } else {
                this.dropItem.currentAreaInfo.children.push(this.drawOrActiveItem);
            }

            //TODO 这里最后停下的数据就是可以操作的数据
            this.checkedEvent.emit(this.drawOrActiveItem)
        }
        // 完了就情况拖拽的id
        this.drawId = null;
        this.dropItem = null;
        // console.log(this.defaultComponentInfo);
    }

    // 递归删除数据
    loopfilter = (arr: any[], valueId: any) => {
        arr.forEach((item, index) => {
            if (item.id === valueId) {
                arr.splice(index, 1)
            }
            if (item.children.length > 0) {
                return this.loopfilter(item.children, valueId)
            }
        })
    }

    // 递归查找数据
    filterData = (drawId: any, arr: any) => {
        for (const item of arr) {
            if (item.id == drawId) return item;
            if (item.children && item.children.length) {
                const item2: any = this.filterData(drawId, item.children);
                if (item2) return item2;
            }
        }
    }


    // 点击事件
    handleActive(event: any, row: any): void {
        event.stopPropagation();
        // console.log(row);
        this.activeId = row.id;
        const activeItem = this.filterData(row.id, this.defaultComponentInfo.component);
        this.drawOrActiveItem = activeItem;
        //TODO这里点击的的数据就是可以操作的数据
        this.checkedEvent.emit(this.drawOrActiveItem)
    }


    // 删除事件
    handleDelete(event: any, row: any): void {
        event.stopPropagation();
        // console.log(row);
        this.loopfilter(this.defaultComponentInfo.component, row.id);
        console.log(this.defaultComponentInfo);
    }

}

4.然后封装了两个简单的拖拽指令,通过拖的指令,到放在哪一个块上的指令。

//draw.ts
import { Directive, ElementRef, HostListener, Input, Output, EventEmitter } from '@angular/core';

import { saveDrawingItem } from '../../utils/db';
@Directive({
    selector: '[appDrag]'
})
export class DragDirective {
    // 拖拽的类型
    @Input() componentType: string = '';
    // 拖拽的id
    @Input() componentId?: string;
    // 拖动占位符
    @Input() dragPlaceholder: HTMLElement | undefined;
    el: ElementRef;

    // 拖拽结束返回参数
    @Output() drawEndEvent: EventEmitter<any> = new EventEmitter();
    // 拖拽开始
    @Output() drawStartEvent: EventEmitter<any> = new EventEmitter();
    canvasConfig = {
        width: 100,
        height: 100,
    };


    constructor(el: ElementRef) {
        this.el = el;
        this.el.nativeElement.setAttribute('draggable', true);
        this.el.nativeElement.style.cursor = 'move';
    }

    // 开始拖拽
    @HostListener('dragstart', ['$event'])
    dragstart(e: any) {
        e.stopPropagation();
        const dt = e.dataTransfer;
        dt.effectAllowed = 'copy';
        dt.setData('text/plain', this.componentType);
        this.dragWidthCustomerImage(e);
        if (this.componentId) {
            this.drawStartEvent.emit({
                id: this.componentId
            })
        }
    }

    // 拖拽完成
    @HostListener('dragend', ['$event'])
    dragend(e: any) {
        e.stopPropagation();
        if (this.componentId) {
            this.drawEndEvent.emit({
                id: this.componentId
            })
        }
        this.resetCanvas(this.dragPlaceholder);
    }

    // 生成拖拽目标的阴影
    dragWidthCustomerImage(event: any) {
        event.dataTransfer.setDragImage(this.dragPlaceholder, 25, 25);
    }

    resetCanvas(canvasEl: any) {

    }
}

//drop指令
import { Directive, ElementRef, EventEmitter, HostListener, Input, Output } from '@angular/core';
import { DropDataInterface } from '../../pages/interfaces/drag.interface';
import { getDrawingItem } from '../../utils/db';
@Directive({
    selector: '[appDrop]'
})
export class DropDirective {
    el: ElementRef;
    @Input()
    dropData!: { id: string; layout: string; };
    @Output() dropEvent: EventEmitter<DropDataInterface> = new EventEmitter();
    @Output() dropEndEvent: EventEmitter<any> = new EventEmitter();



    constructor(el: ElementRef) {
        this.el = el;
    }

    @HostListener('dragover', ['$event'])
    dragOver(e: any) {

        e.preventDefault();
        const target = e.target;
        //因为dragover会发生在其他dom上,所以要判断是不是col
        // if (e.target.matches(".draw-col ")) {
        //     console.log(target);
        //     const prev = target.previousElementSibling();
        //     const next = target.nextElementSibling();
        //     if (prev) {
        //         console.log("前一个",prev);
        //     }
        //     if(next){
        //         console.log("后一个",next);
        //     }

        // }
     
        return false;
    }


    // 拖拽完成
    @HostListener('dragend', ['$event'])
    dragend(e: any) {
        console.log('拽完成')
        e.stopPropagation();
        // console.log(e);
    }

    // 拖拽离开当前选中的div
    @HostListener('dragleave', ['$event'])
    dragleave(e: any) {
        e.preventDefault();
        if (e.target.matches(".drawing-board ") || e.target.matches(".drag-wrapper") || e.target.matches(".drag-wrapper-row") ) {
            console.log('拖拽离开选中div');
            // console.log(document.getElementById('outBox'))
            e.target.style.border = 'none';

        }
    }

    // 拖拽移入后当前选中的div样式
    @HostListener('dragenter', ['$event'])
    dragenter(e: any) {
        e.stopPropagation();
        if (e.target.matches(".drawing-board ") || e.target.matches(".drag-wrapper")  || e.target.matches(".drag-wrapper-row")) {
            console.log('拖拽移入选中div')
            // e.target.style.border = '1px solid red';
        }

    }


    // 拖拽放下
    @HostListener('drop', ['$event'])
    drop(e: any) {
        e.stopPropagation();
        if (e.target.matches(".drawing-board ") || e.target.matches(".drag-wrapper")  || e.target.matches(".drag-wrapper-row")) {
            const text = e.dataTransfer.getData('text');
            console.log('拖拽放下选中div')
            this.dropEvent.emit({
                layout: text,
                currentAreaInfo: this.dropData,
            });
            
        }
    }

}

5.最后附上组件的静态模式数据


export const layoutUiComponents = {
    id: "",
    type: "layout",
    styles: {
        grid: 11,
        type: 'default',
        justify: 'start',
        align: 'top'
    },
    props: {

    },
}

export const inputUiComponents = {
    id: "",
    label: "字段",
    type: "input",
    props: {
    },
    styles: {
        layout: {
            margin: "",
            padding: "",
            grid: 24,
            labelWidth: 104,
            showLabel: true,
        }
    },
    dataBinding: {
        fieldName: "field1",
        fieldLable: "字段1",
        fieldType: "string"
    }
}

1.以上便是能够实现基础简单的拖拽功能,后期还可以拓展更多的组件交互,通过拖入不同的组件来实现拖拽布局,最后保存的json文件,即可作为低代码渲染的数据去渲染,仅仅是demo作为学习参考。
2.这里可以做拓展的方向也有如果实现拖拽排序,参考一个思路就是,把一个组件拖入到另一个组件的头上,然后松下鼠标这一个过程,获取到他放下的那个组件,然后处理他父级中children,进行删除,然后插入这个过程,其实就是数据处理把小标为1的替换到下标为2的位置上,核心就是知道拖的组件下标和放下的组件下标就可以了。
3.这里考虑到from输入框问题,所以得带上双向绑定的key和value,一定要把设计模式下的数据和最后渲染模式下数据理解区分开。
4.仅供参考学习,之前查找的参考资料链接没找到了,如果涉及到抄袭,可联系修改,谢谢。

码砖不易,点赞支持

相关文章

  • 原生拖拽,拖放事件(drag and drop)

    拖拽,拖放事件可以通过拖拽实现数据传递,达到良好的交互效果,如:从操作系统拖拽文件实现文件选择,拖拽实现元素布局的...

  • 简单的拖拽图片

    今天试了一下图片拖拽,拖拽过去伴随着自动计数功能,很简单的小 程序。代码如下: 布局文件

  • Angular之自定义管道

    简介这里简单的介绍了代码极简化,以及制定自己想要的功能通过自定义管道来实现。 2.语法关闭Angular项目新建管...

  • JS实现拖拽功能

    拖拽功能是我们日常项目中常用的效果,今天我们就来研究一下如何实现简单的拖拽功能。想实现拖拽功能其实很简单,主要需要...

  • html5实现拖拽效果

    在此之前,实现拖拽操作都是开发人员自定义逻辑实现的,但是HTML5提供了拖拽API ,使得拖拽操作的实现变得简单。...

  • Android-自定义全局拽托返回

    效果 自定义属性 代码实现 使用 布局xml Java代码

  • 自定义换行右下角带标志TextView

    自定义属性 代码实现 布局使用 代码设置显示内容

  • 百度地图web--拖拽选址

    实现地图拖拽选址功能,百度地图并未像高德地图拖拽选址功能 。由于项目需要,在百度地图的基础上实现简单的拖拽功能。大...

  • 基于ViewGroup的Android可拖拽控件,同时解决和on

    继承自ViewGroup的自定义拖拽控件直接上代码: 布局中引用方式如下(等同于线性布局): 详细解读如下:思路很...

  • 禁止用户拖拽图片或文件到窗口

    在做文件上传功能的时候,如果没有做响应用户拖拽文件的功能的话,建议先禁用拖拽功能。 实现代码: jquery版本(...

网友评论

      本文标题:angular自定义拖拽布局功能,实现简单低代码交互

      本文链接:https://www.haomeiwen.com/subject/rgblrdtx.html